diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 74bea2ded278..c8bbf6aaf554 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -19,6 +19,7 @@ These are the supported libraries and frameworks: | Library/Framework | Auto-instrumented versions | Standalone Library Instrumentation [1] | Semantic Conventions | |---------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| [Aerospike Client](https://github.com/aerospike/aerospike-client-java) | 7.1+ | N/A | [Database Pool Metrics] | | [Akka Actors](https://doc.akka.io/docs/akka/current/typed/index.html) | 2.3+ | N/A | Context propagation | | [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics], Provides `http.route` [2] | | [Alibaba Druid](https://github.com/alibaba/druid) | 1.0+ | [opentelemetry-alibaba-druid-1.0](../instrumentation/alibaba-druid-1.0/library) | [Database Pool Metrics] | diff --git a/instrumentation/aerospike-client/README.md b/instrumentation/aerospike-client/README.md new file mode 100644 index 000000000000..fbaefe6207c7 --- /dev/null +++ b/instrumentation/aerospike-client/README.md @@ -0,0 +1,6 @@ +# Settings for the Aerospike-Client instrumentation + +| System property | Type | Default | Description | +|---------------------------------------------------------------|---------|---------|----------------------------------------------------------------------| +| `otel.instrumentation.aerospike.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental Aerospike client span attributes. | +| `otel.instrumentation.aerospike.experimental-metrics` | Boolean | `false` | Enable the recording of experimental Aerospike client metrics. | diff --git a/instrumentation/aerospike-client/javaagent/build.gradle.kts b/instrumentation/aerospike-client/javaagent/build.gradle.kts new file mode 100644 index 000000000000..f51cf81f2c20 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.aerospike") + module.set("aerospike-client") + versions.set("[7.1.0,)") + assertInverse.set(true) + } +} + +val latestDepTest = findProperty("testLatestDeps") as Boolean + +dependencies { + library("com.aerospike:aerospike-client:7.1.0") + implementation("io.opentelemetry:opentelemetry-api-incubator") + + compileOnly("com.google.auto.value:auto-value-annotations") + annotationProcessor("com.google.auto.value:auto-value") +} + +if (latestDepTest) { + otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_21) + } +} + +tasks { + test { + jvmArgs("-Djava.net.preferIPv4Stack=true") + jvmArgs("-Dotel.instrumentation.aerospike.experimental-span-attributes=true") + jvmArgs("-Dotel.instrumentation.aerospike.experimental-metrics=true") + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientAttributeExtractor.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientAttributeExtractor.java new file mode 100644 index 000000000000..56f97060dd43 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientAttributeExtractor.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.ResultCode; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequest; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeSemanticAttributes; +import javax.annotation.Nullable; + +final class AerospikeClientAttributeExtractor + implements AttributesExtractor { + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, AerospikeRequest aerospikeRequest) { + attributes.put(AerospikeSemanticAttributes.AEROSPIKE_SET_NAME, aerospikeRequest.getSet()); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + AerospikeRequest aerospikeRequest, + @Nullable Void unused, + @Nullable Throwable error) { + if (aerospikeRequest.getNode() != null) { + String nodeName = aerospikeRequest.getNode().getName(); + attributes.put(AerospikeSemanticAttributes.AEROSPIKE_NODE_NAME, nodeName); + } + + if (error != null) { + if (error instanceof AerospikeException) { + AerospikeException aerospikeException = (AerospikeException) error; + attributes.put( + AerospikeSemanticAttributes.AEROSPIKE_STATUS, aerospikeException.getResultCode()); + } else { + attributes.put(AerospikeSemanticAttributes.AEROSPIKE_STATUS, ResultCode.CLIENT_ERROR); + } + } else { + attributes.put(AerospikeSemanticAttributes.AEROSPIKE_STATUS, ResultCode.OK); + } + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentation.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentation.java new file mode 100644 index 000000000000..89841655f5bb --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentation.java @@ -0,0 +1,128 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.aerospike.AerospikeSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.aerospike.internal.CustomElementMatcher.iterableHasAtLeastOne; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesGenericArguments; + +import com.aerospike.client.Key; +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequest; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequestContext; +import java.util.Locale; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class AerospikeClientInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return hasSuperType(named("com.aerospike.client.IAerospikeClient")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(takesGenericArguments(iterableHasAtLeastOne(named("com.aerospike.client.Key")))) + .and( + not( + takesGenericArguments( + iterableHasAtLeastOne(named("com.aerospike.client.async.EventLoop"))))), + this.getClass().getName() + "$SyncCommandAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(takesGenericArguments(iterableHasAtLeastOne(named("com.aerospike.client.Key")))) + .and( + takesGenericArguments( + iterableHasAtLeastOne(named("com.aerospike.client.async.EventLoop")))), + this.getClass().getName() + "$AsyncCommandAdvice"); + } + + @SuppressWarnings("unused") + public static class SyncCommandAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void startInstrumentation( + @Advice.Origin("#m") String methodName, + @Advice.AllArguments Object[] args, + @Advice.Local("AerospikeContext") AerospikeRequestContext requestContext) { + Key key = null; + for (Object object : args) { + if (object instanceof Key) { + key = (Key) object; + } + } + if (key == null) { + return; + } + Context parentContext = currentContext(); + AerospikeRequest request = + AerospikeRequest.create(methodName.toUpperCase(Locale.ROOT), key.namespace, key.setName); + if (!instrumenter().shouldStart(parentContext, request)) { + return; + } + Context context = instrumenter().start(parentContext, request); + requestContext = AerospikeRequestContext.attach(request, context); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopInstrumentation( + @Advice.Thrown Throwable ex, + @Advice.Local("AerospikeContext") AerospikeRequestContext requestContext) { + requestContext.setThrowable(ex); + requestContext.detachAndEnd(); + } + } + + @SuppressWarnings("unused") + public static class AsyncCommandAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void startInstrumentation( + @Advice.Origin("#m") String methodName, + @Advice.AllArguments Object[] args, + @Advice.Local("AerospikeContext") AerospikeRequestContext requestContext) { + Key key = null; + for (Object object : args) { + if (object instanceof Key) { + key = (Key) object; + } + } + if (key == null) { + return; + } + Context parentContext = currentContext(); + AerospikeRequest request = + AerospikeRequest.create(methodName.toUpperCase(Locale.ROOT), key.namespace, key.setName); + if (!instrumenter().shouldStart(parentContext, request)) { + return; + } + Context context = instrumenter().start(parentContext, request); + requestContext = AerospikeRequestContext.attach(request, context); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopInstrumentaionIfError( + @Advice.Thrown Throwable ex, + @Advice.Local("AerospikeContext") AerospikeRequestContext requestContext) { + if (ex != null) { + requestContext.setThrowable(ex); + requestContext.detachAndEnd(); + } + } + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentationModule.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentationModule.java new file mode 100644 index 000000000000..038d8ddb170c --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeClientInstrumentationModule.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +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 java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class AerospikeClientInstrumentationModule extends InstrumentationModule { + + public AerospikeClientInstrumentationModule() { + super("aerospike-client"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return hasClassesNamed("com.aerospike.client.metrics.LatencyType"); + } + + @Override + public List typeInstrumentations() { + return asList( + new AerospikeClientInstrumentation(), + new SyncCommandInstrumentation(), + new AsyncCommandInstrumentation()); + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeDbAttributesGetter.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeDbAttributesGetter.java new file mode 100644 index 000000000000..a59047c4d775 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeDbAttributesGetter.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequest; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeSemanticAttributes; +import javax.annotation.Nullable; + +final class AerospikeDbAttributesGetter implements DbClientAttributesGetter { + + @Override + public String getDbSystem(AerospikeRequest request) { + return AerospikeSemanticAttributes.DbSystemValues.AEROSPIKE; + } + + @Override + public String getDbOperationName(AerospikeRequest request) { + return request.getOperation(); + } + + @Nullable + @Override + public String getDbNamespace(AerospikeRequest request) { + return request.getNamespace(); + } + + @Deprecated + @Nullable + @Override + public String getUser(AerospikeRequest aerospikeRequest) { + return null; + } + + @Deprecated + @Nullable + @Override + public String getConnectionString(AerospikeRequest aerospikeRequest) { + return null; + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeNetworkAttributesGetter.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeNetworkAttributesGetter.java new file mode 100644 index 000000000000..2cc53fbb46f8 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeNetworkAttributesGetter.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import com.aerospike.client.cluster.Node; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequest; +import java.net.InetSocketAddress; +import javax.annotation.Nullable; + +final class AerospikeNetworkAttributesGetter + implements NetworkAttributesGetter { + + @Override + @Nullable + public InetSocketAddress getNetworkPeerInetSocketAddress( + AerospikeRequest aerospikeRequest, @Nullable Void unused) { + Node node = aerospikeRequest.getNode(); + if (node != null) { + return node.getAddress(); + } + return null; + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeSingletons.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeSingletons.java new file mode 100644 index 000000000000..9672a4802e92 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AerospikeSingletons.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequest; +import io.opentelemetry.javaagent.instrumentation.aerospike.metrics.AerospikeMetrics; + +public final class AerospikeSingletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.aerospike-client"; + + private static final Instrumenter INSTRUMENTER; + + static { + AerospikeDbAttributesGetter aerospikeDbAttributesGetter = new AerospikeDbAttributesGetter(); + NetworkAttributesGetter netAttributesGetter = + new AerospikeNetworkAttributesGetter(); + + InstrumenterBuilder builder = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + DbClientSpanNameExtractor.create(aerospikeDbAttributesGetter)) + .addAttributesExtractor(DbClientAttributesExtractor.create(aerospikeDbAttributesGetter)) + .addAttributesExtractor(NetworkAttributesExtractor.create(netAttributesGetter)); + InstrumentationConfig instrumentationConfig = AgentInstrumentationConfig.get(); + if (instrumentationConfig.getBoolean( + "otel.instrumentation.aerospike.experimental-span-attributes", false)) { + builder.addAttributesExtractor(new AerospikeClientAttributeExtractor()); + } + if (instrumentationConfig.getBoolean( + "otel.instrumentation.aerospike.experimental-metrics", false)) { + builder.addOperationMetrics(AerospikeMetrics.get()); + } + + INSTRUMENTER = builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + private AerospikeSingletons() {} +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AsyncCommandInstrumentation.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AsyncCommandInstrumentation.java new file mode 100644 index 000000000000..0206635a2981 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/AsyncCommandInstrumentation.java @@ -0,0 +1,129 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperClass; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.cluster.Node; +import com.aerospike.client.command.Command; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequestContext; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class AsyncCommandInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.aerospike.client.async.AsyncCommand"); + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperClass(named("com.aerospike.client.async.AsyncCommand")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isConstructor().and(isPublic()), this.getClass().getName() + "$ConstructorAdvice"); + + transformer.applyAdviceToMethod( + isMethod().and(named("getNode")), this.getClass().getName() + "$GetNodeAdvice"); + + transformer.applyAdviceToMethod( + isMethod().and(named("onSuccess")), this.getClass().getName() + "$GetOnSuccessAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and( + named("onFailure") + .and(takesArgument(0, named("com.aerospike.client.AerospikeException")))), + this.getClass().getName() + "$GetOnFailureAdvice"); + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getCommand(@Advice.This Command command) { + VirtualField virtualField = + VirtualField.find(Command.class, AerospikeRequestContext.class); + AerospikeRequestContext requestContext = virtualField.get(command); + // If the AerospikeRequestContext is already there in VirtualField then we do not need + // to override it when constructor of other subclasses of AsyncCommand executes as + // AsyncCommand follows multilevel inheritance. + if (requestContext != null) { + return; + } + requestContext = AerospikeRequestContext.current(); + if (requestContext == null) { + return; + } + virtualField.set(command, requestContext); + requestContext.detachContext(); + } + } + + @SuppressWarnings("unused") + public static class GetNodeAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getNode(@Advice.Return Node node, @Advice.This Command command) { + VirtualField virtualField = + VirtualField.find(Command.class, AerospikeRequestContext.class); + AerospikeRequestContext requestContext = virtualField.get(command); + if (requestContext == null || requestContext.getRequest().getNode() != null) { + return; + } + requestContext.getRequest().setNode(node); + } + } + + @SuppressWarnings("unused") + public static class GetOnSuccessAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void stopInstrumentation(@Advice.This Command command) { + VirtualField virtualField = + VirtualField.find(Command.class, AerospikeRequestContext.class); + AerospikeRequestContext requestContext = virtualField.get(command); + if (requestContext == null) { + return; + } + virtualField.set(command, null); + requestContext.endSpan(); + } + } + + @SuppressWarnings("unused") + public static class GetOnFailureAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void stopInstrumentation( + @Advice.Argument(0) AerospikeException ae, @Advice.This Command command) { + VirtualField virtualField = + VirtualField.find(Command.class, AerospikeRequestContext.class); + AerospikeRequestContext requestContext = virtualField.get(command); + if (requestContext == null) { + return; + } + virtualField.set(command, null); + requestContext.setThrowable(ae); + requestContext.endSpan(); + } + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/SyncCommandInstrumentation.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/SyncCommandInstrumentation.java new file mode 100644 index 000000000000..86b6332b8c62 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/SyncCommandInstrumentation.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperClass; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.aerospike.client.cluster.Node; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeRequestContext; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SyncCommandInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.aerospike.client.command.SyncCommand"); + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperClass(named("com.aerospike.client.command.SyncCommand")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("getNode")), this.getClass().getName() + "$GetNodeAdvice"); + } + + @SuppressWarnings("unused") + public static class GetNodeAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getNode(@Advice.Return Node node) { + AerospikeRequestContext requestContext = AerospikeRequestContext.current(); + if (requestContext == null || requestContext.getRequest().getNode() != null) { + return; + } + requestContext.getRequest().setNode(node); + } + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequest.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequest.java new file mode 100644 index 000000000000..909e563075e6 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.internal; + +import com.aerospike.client.cluster.Node; +import com.google.auto.value.AutoValue; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@AutoValue +public abstract class AerospikeRequest { + private Node node; + + public static AerospikeRequest create(String operation, String namespace, String set) { + return new AutoValue_AerospikeRequest(operation, namespace, set); + } + + public abstract String getOperation(); + + public abstract String getNamespace(); + + public abstract String getSet(); + + public void setNode(Node node) { + this.node = node; + } + + public Node getNode() { + return this.node; + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequestContext.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequestContext.java new file mode 100644 index 000000000000..2eb30d2b6a06 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeRequestContext.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.internal; + +import static io.opentelemetry.javaagent.instrumentation.aerospike.AerospikeSingletons.instrumenter; + +import io.opentelemetry.context.Context; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AerospikeRequestContext { + private static final ThreadLocal contextThreadLocal = + new ThreadLocal<>(); + private AerospikeRequest request; + private Context context; + private Throwable throwable; + + private AerospikeRequestContext() {} + + public static AerospikeRequestContext attach(AerospikeRequest request, Context context) { + AerospikeRequestContext requestContext = current(); + if (requestContext != null) { + requestContext.detachContext(); + } + requestContext = new AerospikeRequestContext(); + requestContext.request = request; + requestContext.context = context; + contextThreadLocal.set(requestContext); + return requestContext; + } + + public void detachAndEnd() { + detachContext(); + if (request != null) { + endSpan(); + } + } + + public void detachContext() { + contextThreadLocal.remove(); + } + + public static AerospikeRequestContext current() { + return contextThreadLocal.get(); + } + + public void endSpan() { + instrumenter().end(context, request, null, throwable); + } + + public AerospikeRequest getRequest() { + return request; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeSemanticAttributes.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeSemanticAttributes.java new file mode 100644 index 000000000000..4cf8170e0dc7 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/AerospikeSemanticAttributes.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.internal; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AerospikeSemanticAttributes { + private AerospikeSemanticAttributes() {} + + public static final AttributeKey AEROSPIKE_STATUS = longKey("db.status"); + public static final AttributeKey AEROSPIKE_SET_NAME = stringKey("db.set.name"); + public static final AttributeKey AEROSPIKE_NODE_NAME = stringKey("db.node.name"); + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static final class DbSystemValues { + public static final String AEROSPIKE = "aerospike"; + + private DbSystemValues() {} + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/CustomElementMatcher.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/CustomElementMatcher.java new file mode 100644 index 000000000000..b3b5881a4e6e --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/internal/CustomElementMatcher.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.internal; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class CustomElementMatcher { + private CustomElementMatcher() {} + + public static ElementMatcher> iterableHasAtLeastOne( + ElementMatcher matcher) { + return iterable -> { + for (TypeDescription.Generic typeDescription : iterable) { + if (matcher.matches(typeDescription)) { + return true; + } + } + return false; + }; + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetrics.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetrics.java new file mode 100644 index 000000000000..893e2028ac61 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetrics.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.metrics; + +import static java.util.logging.Level.FINE; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public final class AerospikeMetrics implements OperationListener { + private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1); + + private static final ContextKey AEROSPIKE_CLIENT_METRICS_STATE = + ContextKey.named("aerospike-client-metrics-state"); + + private static final Logger logger = Logger.getLogger(AerospikeMetrics.class.getName()); + + private final LongUpDownCounter concurrencyUpDownCounter; + + private final DoubleHistogram clientLatencyHistogram; + + private AerospikeMetrics(Meter meter) { + LongUpDownCounterBuilder concurrencyUpDownCounterBuilder = + meter + .upDownCounterBuilder("aerospike.concurrency") + .setDescription("Aerospike Concurrent Requests"); + AerospikeMetricsAdvice.applyConcurrencyUpDownCounterAdvice(concurrencyUpDownCounterBuilder); + concurrencyUpDownCounter = concurrencyUpDownCounterBuilder.build(); + + DoubleHistogramBuilder durationBuilder = + meter + .histogramBuilder("aerospike.client.duration") + .setDescription("Aerospike Response Latency") + .setUnit("ms"); + AerospikeMetricsAdvice.applyClientDurationAdvice(durationBuilder); + clientLatencyHistogram = durationBuilder.build(); + } + + public static OperationMetrics get() { + return AerospikeMetrics::new; + } + + @Override + public Context onStart(Context context, Attributes startAttributes, long startNanos) { + concurrencyUpDownCounter.add(1, startAttributes, context); + return context.with( + AEROSPIKE_CLIENT_METRICS_STATE, + new AutoValue_AerospikeMetrics_State(startAttributes, startNanos)); + } + + @Override + public void onEnd(Context context, Attributes endAttributes, long endNanos) { + State state = context.get(AEROSPIKE_CLIENT_METRICS_STATE); + if (state == null) { + logger.log( + FINE, + "No state present when ending context {0}. Cannot record Aerospike End Call metrics.", + context); + return; + } + concurrencyUpDownCounter.add(-1, state.startAttributes(), context); + Attributes mergedAttributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); + clientLatencyHistogram.record( + (endNanos - state.startTimeNanos()) / NANOS_PER_MS, mergedAttributes, context); + } + + @AutoValue + abstract static class State { + + abstract Attributes startAttributes(); + + abstract long startTimeNanos(); + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetricsAdvice.java b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetricsAdvice.java new file mode 100644 index 000000000000..eb8a3872b0bc --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/aerospike/metrics/AerospikeMetricsAdvice.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.metrics; + +import static io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeSemanticAttributes.AEROSPIKE_NODE_NAME; +import static io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeSemanticAttributes.AEROSPIKE_SET_NAME; +import static io.opentelemetry.javaagent.instrumentation.aerospike.internal.AerospikeSemanticAttributes.AEROSPIKE_STATUS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder; +import io.opentelemetry.api.incubator.metrics.ExtendedLongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("deprecation") // using deprecated semconv +final class AerospikeMetricsAdvice { + private AerospikeMetricsAdvice() {} + + static void applyConcurrencyUpDownCounterAdvice(LongUpDownCounterBuilder builder) { + if (!(builder instanceof ExtendedLongUpDownCounterBuilder)) { + return; + } + + List> attributes = new ArrayList<>(); + attributes.add(DB_SYSTEM); + attributes.add(DB_OPERATION); + attributes.add(DB_NAME); + attributes.add(AEROSPIKE_SET_NAME); + + ((ExtendedLongUpDownCounterBuilder) builder).setAttributesAdvice(attributes); + } + + static void applyClientDurationAdvice(DoubleHistogramBuilder builder) { + if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { + return; + } + + List> attributes = new ArrayList<>(); + attributes.add(DB_SYSTEM); + attributes.add(DB_OPERATION); + attributes.add(DB_NAME); + attributes.add(AEROSPIKE_SET_NAME); + attributes.add(AEROSPIKE_STATUS); + attributes.add(NETWORK_PEER_ADDRESS); + attributes.add(NETWORK_PEER_PORT); + attributes.add(NETWORK_TYPE); + attributes.add(AEROSPIKE_NODE_NAME); + + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(attributes); + } +} diff --git a/instrumentation/aerospike-client/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/aerospike/v7_0/AerospikeClientTest.java b/instrumentation/aerospike-client/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/aerospike/v7_0/AerospikeClientTest.java new file mode 100644 index 000000000000..f5c29b4e11e4 --- /dev/null +++ b/instrumentation/aerospike-client/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/aerospike/v7_0/AerospikeClientTest.java @@ -0,0 +1,259 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.aerospike.v7_0; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; +import static java.util.Collections.singletonList; + +import com.aerospike.client.AerospikeClient; +import com.aerospike.client.Bin; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.async.EventPolicy; +import com.aerospike.client.async.NioEventLoops; +import com.aerospike.client.policy.ClientPolicy; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +@SuppressWarnings("deprecation") // using deprecated semconv +class AerospikeClientTest { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + public static final AttributeKey AEROSPIKE_STATUS = longKey("db.status"); + public static final AttributeKey AEROSPIKE_SET_NAME = stringKey("db.set.name"); + public static final AttributeKey AEROSPIKE_NODE_NAME = stringKey("db.node.name"); + + static GenericContainer aerospikeServer = + new GenericContainer<>("aerospike/aerospike-server:6.2.0.0") + .withExposedPorts(3000) + .waitingFor(Wait.forLogMessage(".*replication factor is 1.*", 1)); + + static int port; + + static AerospikeClient aerospikeClient; + + @BeforeAll + static void setupSpec() { + aerospikeServer.start(); + port = 3000; + ClientPolicy clientPolicy = new ClientPolicy(); + int eventLoopSize = Runtime.getRuntime().availableProcessors(); + EventPolicy eventPolicy = new EventPolicy(); + eventPolicy.commandsPerEventLoop = 2; + clientPolicy.eventLoops = new NioEventLoops(eventPolicy, eventLoopSize); + clientPolicy.maxConnsPerNode = eventLoopSize; + clientPolicy.failIfNotConnected = true; + aerospikeClient = new AerospikeClient(clientPolicy, "localhost", 3000); + } + + @AfterAll + static void cleanupSpec() { + aerospikeClient.close(); + aerospikeServer.stop(); + } + + @BeforeEach + void setup() { + testing.clearData(); + } + + @Test + void asyncCommand() { + Key aerospikeKey = new Key("test", "test-set", "data1"); + aerospikeClient.put(null, null, null, aerospikeKey, new Bin("bin1", "value1")); + + AtomicReference instrumentationName = new AtomicReference<>(); + testing.waitAndAssertTraces( + trace -> { + instrumentationName.set(trace.getSpan(0).getInstrumentationScopeInfo().getName()); + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("PUT test") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, "aerospike"), + equalTo(DB_OPERATION, "PUT"), + equalTo(NETWORK_PEER_PORT, port), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_TYPE, "ipv4"), + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(AEROSPIKE_STATUS, 0))); + }); + + testing.waitAndAssertMetrics( + instrumentationName.get(), + "aerospike.concurrency", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + upDownCounter -> + upDownCounter.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributesSatisfying( + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(DB_OPERATION, "PUT"), + equalTo(DB_SYSTEM, "aerospike")))))); + + testing.waitAndAssertMetrics( + instrumentationName.get(), + "aerospike.client.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributesSatisfying( + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(DB_OPERATION, "PUT"), + equalTo(DB_SYSTEM, "aerospike"), + equalTo(AEROSPIKE_STATUS, 0), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_PEER_PORT, 3000), + equalTo(NETWORK_TYPE, "ipv4")))))); + } + + @Test + void syncCommand() { + Key aerospikeKey = new Key("test", "test-set", "data2"); + aerospikeClient.put(null, aerospikeKey, new Bin("bin2", "value2")); + List bins = singletonList("bin2"); + Record aerospikeRecord = aerospikeClient.get(null, aerospikeKey, bins.toArray(new String[0])); + assertThat(aerospikeRecord.getString("bin2")).isEqualTo("value2"); + + AtomicReference instrumentationName = new AtomicReference<>(); + + testing.waitAndAssertTraces( + trace -> { + instrumentationName.set(trace.getSpan(0).getInstrumentationScopeInfo().getName()); + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("PUT test") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, "aerospike"), + equalTo(DB_OPERATION, "PUT"), + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(AEROSPIKE_STATUS, 0), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_PEER_PORT, 3000), + equalTo(NETWORK_TYPE, "ipv4"))); + }, + trace -> { + instrumentationName.set(trace.getSpan(0).getInstrumentationScopeInfo().getName()); + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET test") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, "aerospike"), + equalTo(DB_OPERATION, "GET"), + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(AEROSPIKE_STATUS, 0), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_PEER_PORT, 3000), + equalTo(NETWORK_TYPE, "ipv4"))); + }); + + testing.waitAndAssertMetrics( + instrumentationName.get(), + "aerospike.concurrency", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + upDownCounter -> + upDownCounter.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributesSatisfying( + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(DB_OPERATION, "PUT"), + equalTo(DB_SYSTEM, "aerospike")), + point -> + point + .hasValue(0) + .hasAttributesSatisfying( + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(DB_OPERATION, "GET"), + equalTo(DB_SYSTEM, "aerospike")))))); + + testing.waitAndAssertMetrics( + instrumentationName.get(), + "aerospike.client.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributesSatisfying( + equalTo(DB_SYSTEM, "aerospike"), + equalTo(DB_NAME, "test"), + equalTo(DB_OPERATION, "PUT"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(AEROSPIKE_STATUS, 0), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_PEER_PORT, 3000), + equalTo(NETWORK_TYPE, "ipv4"), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202")), + point -> + point.hasAttributesSatisfying( + equalTo(DB_NAME, "test"), + equalTo(AEROSPIKE_SET_NAME, "test-set"), + equalTo(DB_OPERATION, "GET"), + equalTo(DB_SYSTEM, "aerospike"), + equalTo(AEROSPIKE_STATUS, 0), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + equalTo(NETWORK_PEER_PORT, 3000), + equalTo(NETWORK_TYPE, "ipv4"), + equalTo(AEROSPIKE_NODE_NAME, "BB9040017AC4202")))))); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index aa3dabeccf14..53ae5767c774 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -172,6 +172,7 @@ include(":smoke-tests-otel-starter:spring-boot-reactive-2") include(":smoke-tests-otel-starter:spring-boot-reactive-3") include(":smoke-tests-otel-starter:spring-boot-reactive-common") +include(":instrumentation:aerospike-client:javaagent") include(":instrumentation:akka:akka-actor-2.3:javaagent") include(":instrumentation:akka:akka-actor-fork-join-2.5:javaagent") include(":instrumentation:akka:akka-http-10.0:javaagent")