diff --git a/instrumentation/quarkus-rest-3.0.0/build.gradle b/instrumentation/quarkus-rest-3.0.0/build.gradle new file mode 100644 index 0000000000..d9f2b9d3e8 --- /dev/null +++ b/instrumentation/quarkus-rest-3.0.0/build.gradle @@ -0,0 +1,79 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation('io.quarkus:quarkus-resteasy-reactive:3.0.0.Final') +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.quarkus-rest-3.0.0', + 'Implementation-Title-Alias': 'quarkus'} +} + +verifyInstrumentation { + passesOnly 'io.quarkus:quarkus-resteasy-reactive:[3.0.0,3.9.0)' + passesOnly 'io.quarkus:quarkus-rest:[3.9.0,)' + excludeRegex '.*CR.*' + excludeRegex '.*Alpha[0-9]' + excludeRegex '.*Beta[0-9]' + + // quarkus-resteasy-reactive: exclude all pre-3.x versions (module does not apply) + exclude 'io.quarkus:quarkus-resteasy-reactive:[1.11.0.Final,3.0.0.Final)' + // quarkus-resteasy-reactive: keep only last patch of each minor + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.0.0.Final,3.0.3.Final]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.1.0.Final,3.1.2.Final]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.2.0.Final,3.2.11.Final]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.3.0,3.3.2]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.4.0,3.4.2]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.5.0,3.5.2]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.6.0,3.6.8]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.7.0,3.7.3]' + exclude 'io.quarkus:quarkus-resteasy-reactive:[3.8.0,3.8.6]' + + // quarkus-rest: keep only last patch of each minor; 3.37.x left open to catch new releases + exclude 'io.quarkus:quarkus-rest:[3.9.0,3.9.4]' + exclude 'io.quarkus:quarkus-rest:[3.10.0,3.10.1]' + exclude 'io.quarkus:quarkus-rest:[3.11.0,3.11.2]' + exclude 'io.quarkus:quarkus-rest:[3.12.0,3.12.2]' + exclude 'io.quarkus:quarkus-rest:[3.13.0,3.13.2]' + exclude 'io.quarkus:quarkus-rest:[3.14.0,3.14.3]' + exclude 'io.quarkus:quarkus-rest:[3.15.0,3.15.6.2]' + exclude 'io.quarkus:quarkus-rest:[3.16.0,3.16.3]' + exclude 'io.quarkus:quarkus-rest:[3.17.0,3.17.7]' + exclude 'io.quarkus:quarkus-rest:[3.18.0,3.18.3]' + exclude 'io.quarkus:quarkus-rest:[3.19.0,3.19.3]' + exclude 'io.quarkus:quarkus-rest:[3.20.0,3.20.6.1]' + exclude 'io.quarkus:quarkus-rest:[3.21.0,3.21.3]' + exclude 'io.quarkus:quarkus-rest:[3.22.0,3.22.2]' + exclude 'io.quarkus:quarkus-rest:[3.23.0,3.23.3]' + exclude 'io.quarkus:quarkus-rest:[3.24.0,3.24.4]' + exclude 'io.quarkus:quarkus-rest:[3.25.0,3.25.3]' + exclude 'io.quarkus:quarkus-rest:[3.26.0,3.26.3]' + exclude 'io.quarkus:quarkus-rest:[3.27.0,3.27.4]' + exclude 'io.quarkus:quarkus-rest:[3.28.0,3.28.4]' + exclude 'io.quarkus:quarkus-rest:[3.29.0,3.29.3]' + exclude 'io.quarkus:quarkus-rest:[3.30.0,3.30.7]' + exclude 'io.quarkus:quarkus-rest:[3.31.0,3.31.3]' + exclude 'io.quarkus:quarkus-rest:[3.32.0,3.32.3]' + exclude 'io.quarkus:quarkus-rest:[3.33.0,3.33.2]' + exclude 'io.quarkus:quarkus-rest:[3.34.0,3.34.6]' + exclude 'io.quarkus:quarkus-rest:[3.35.0,3.35.3]' + exclude 'io.quarkus:quarkus-rest:[3.36.0,3.36.2]' + // The next excludes will be 3.37.x +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +test { + // These instrumentation tests only run on Java 17+ regardless of the -PtestN gradle property that is set. + onlyIf { + !project.hasProperty('test8') && !project.hasProperty('test11') + } +} + +site { + title 'Quarkus' + type 'Framework' +} diff --git a/instrumentation/quarkus-rest-3.0.0/src/main/java/com/nr/agent/instrumentation/QuarkusUtils.java b/instrumentation/quarkus-rest-3.0.0/src/main/java/com/nr/agent/instrumentation/QuarkusUtils.java new file mode 100644 index 0000000000..75b76b2354 --- /dev/null +++ b/instrumentation/quarkus-rest-3.0.0/src/main/java/com/nr/agent/instrumentation/QuarkusUtils.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.TransactionNamePriority; +import org.jboss.resteasy.reactive.server.mapping.RuntimeResource; +import org.jboss.resteasy.reactive.server.mapping.URITemplate; + +public class QuarkusUtils { + // Keys owned by the vertx-web-3.8.3 module — used to bridge into its transaction and + // suppress its name in favor of the JAX-RS route template. + public static final String VERTX_TOKEN_KEY = "newrelic-token"; + public static final String VERTX_PATH_KEY = "newrelic-path"; + + public static void setTransactionName(RuntimeResource resource) { + if (resource != null) { + URITemplate classPath = resource.getClassPath(); + URITemplate path = resource.getPath(); + String httpMethod = resource.getHttpMethod(); + + StringBuilder sb = new StringBuilder(); + if (classPath != null && classPath.template != null && !classPath.template.isEmpty()) { + sb.append(classPath.template); + } + if (path != null && path.template != null && !path.template.isEmpty()) { + sb.append(path.template); + } + if (httpMethod != null && !httpMethod.isEmpty()) { + sb.append(" (").append(httpMethod).append(")"); + } + + // We use CUSTOM_LOW here to prevent the Vertx module from overwriting the transaction name + // when a quarkus error occurs. CUSTOM_LOW is priority 11; FRAMEWORK_HIGH is 10. + if (sb.length() > 0) { + NewRelic.getAgent().getTransaction() + .setTransactionName(TransactionNamePriority.CUSTOM_LOW, false, "Quarkus", sb.toString()); + } + } + } +} diff --git a/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/handlers/InvocationHandler_Instrumentation.java b/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/handlers/InvocationHandler_Instrumentation.java new file mode 100644 index 0000000000..a4c834d245 --- /dev/null +++ b/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/handlers/InvocationHandler_Instrumentation.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.jboss.resteasy.reactive.server.handlers; + +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.QuarkusUtils; +import io.vertx.ext.web.RoutingContext; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; + +@Weave(originalName = "org.jboss.resteasy.reactive.server.handlers.InvocationHandler") +public class InvocationHandler_Instrumentation { + + @Trace(async = true) + public void handle(ResteasyReactiveRequestContext requestContext) { + RoutingContext routingContext = requestContext.serverRequest().unwrap(RoutingContext.class); + if (routingContext != null) { + Token vertxToken = routingContext.get(QuarkusUtils.VERTX_TOKEN_KEY); + if (vertxToken != null) { + vertxToken.link(); + } + + // Clears the Vert.x path queue so the vertx-web module's headers-end handler + // doesn't overwrite the JAX-RS route template name we set below. + routingContext.data().remove(QuarkusUtils.VERTX_PATH_KEY); + } + + QuarkusUtils.setTransactionName(requestContext.getTarget()); + Weaver.callOriginal(); + } +} diff --git a/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/vertx/ResteasyReactiveVertxHandler_Instrumentation.java b/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/vertx/ResteasyReactiveVertxHandler_Instrumentation.java new file mode 100644 index 0000000000..49cf7f385e --- /dev/null +++ b/instrumentation/quarkus-rest-3.0.0/src/main/java/org/jboss/resteasy/reactive/server/vertx/ResteasyReactiveVertxHandler_Instrumentation.java @@ -0,0 +1,29 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.jboss.resteasy.reactive.server.vertx; + +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.QuarkusUtils; +import io.vertx.ext.web.RoutingContext; + +@Weave(originalName = "org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler", type = MatchType.ExactClass) +public class ResteasyReactiveVertxHandler_Instrumentation { + + // We're going to piggyback off of the transaction started in the vertx-web module + @Trace(async = true) + public void handle(RoutingContext event) { + Token vertxToken = event.get(QuarkusUtils.VERTX_TOKEN_KEY); + if (vertxToken != null) { + vertxToken.link(); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/build.gradle b/instrumentation/smallrye-mutiny-1.0.0/build.gradle new file mode 100644 index 0000000000..871630a316 --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/build.gradle @@ -0,0 +1,47 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation('io.smallrye.reactive:mutiny:0.14.0') +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.smallrye-mutiny-0.14.0' } +} + +verifyInstrumentation { + passesOnly 'io.smallrye.reactive:mutiny:[1.0.0,)' + excludeRegex '.*Alpha.*' + excludeRegex '.*Beta.*' + excludeRegex '.*RC.*' + excludeRegex '.*milestone.*' + excludeRegex '.*-M[0-9].*' + + // Ignore all 0.x.x versions + excludeRegex 'io.smallrye.reactive:mutiny:0\\..*' + + + // Keep only last patch of each minor ver to reduce verifier task size + exclude 'io.smallrye.reactive:mutiny:[1.1.0,1.1.2)' + exclude 'io.smallrye.reactive:mutiny:[1.3.0,1.3.1)' + exclude 'io.smallrye.reactive:mutiny:[2.3.0,2.3.1)' + exclude 'io.smallrye.reactive:mutiny:[2.5.0,2.5.8)' + exclude 'io.smallrye.reactive:mutiny:[2.6.0,2.6.2)' + // 2.7.0 and 2.8.0 are singletons — no excludes needed yet +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +test { + // These instrumentation tests only run on Java 17+ regardless of the -PtestN gradle property that is set. + onlyIf { + !project.hasProperty('test8') && !project.hasProperty('test11') + } +} + +site { + title 'Quarkus' + type 'Framework' +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/com/nr/agent/instrumentation/SmallRyeUtils.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/com/nr/agent/instrumentation/SmallRyeUtils.java new file mode 100644 index 0000000000..e39b36492c --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/com/nr/agent/instrumentation/SmallRyeUtils.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation; + + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +import java.util.Map; + +public class SmallRyeUtils { + // This replaces the usage of @NewField in the Subscriber weave classes + private static final Map tokenMap = AgentBridge.collectionFactory.createConcurrentWeakKeyedMap(); + + public static void assignTokenToSubscriber(Object subscriber) { + if (subscriber != null) { + if (getToken(subscriber) == null) { + Token token = NewRelic.getAgent().getTransaction().getToken(); + if (token.isActive()) { + putToken(subscriber, token); + } else { + token.expire(); + } + } + } + } + + public static void putToken(Object key, Token token) { + tokenMap.put(key, token); + } + + public static Token getToken(Object key) { + return tokenMap.get(key); + } + + // Here to allow proactive removal of tokens if possible + public static void removeToken(Object key) { + tokenMap.remove(key); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiEmitOnOp_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiEmitOnOp_Instrumentation.java new file mode 100644 index 0000000000..8023fcc9ff --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiEmitOnOp_Instrumentation.java @@ -0,0 +1,21 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.operators.multi; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; +import io.smallrye.mutiny.subscription.MultiSubscriber_Instrumentation; + +@Weave(originalName = "io.smallrye.mutiny.operators.multi.MultiEmitOnOp", type = MatchType.ExactClass) +public class MultiEmitOnOp_Instrumentation { + public void subscribe(MultiSubscriber_Instrumentation subscriber) { + SmallRyeUtils.assignTokenToSubscriber(subscriber); + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiSubscribeOnOp_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiSubscribeOnOp_Instrumentation.java new file mode 100644 index 0000000000..d98d17dc84 --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/multi/MultiSubscribeOnOp_Instrumentation.java @@ -0,0 +1,21 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.operators.multi; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; +import io.smallrye.mutiny.subscription.MultiSubscriber_Instrumentation; + +@Weave(originalName = "io.smallrye.mutiny.operators.multi.MultiSubscribeOnOp", type = MatchType.ExactClass) +public class MultiSubscribeOnOp_Instrumentation { + public void subscribe(MultiSubscriber_Instrumentation subscriber) { + SmallRyeUtils.assignTokenToSubscriber(subscriber); + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniEmitOn_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniEmitOn_Instrumentation.java new file mode 100644 index 0000000000..1b580f9fc8 --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniEmitOn_Instrumentation.java @@ -0,0 +1,21 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.operators.uni; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; +import io.smallrye.mutiny.subscription.UniSubscriber_Instrumentation; + +@Weave(originalName = "io.smallrye.mutiny.operators.uni.UniEmitOn", type = MatchType.ExactClass) +public class UniEmitOn_Instrumentation { + public void subscribe(UniSubscriber_Instrumentation subscriber) { + SmallRyeUtils.assignTokenToSubscriber(subscriber); + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniRunSubscribeOn_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniRunSubscribeOn_Instrumentation.java new file mode 100644 index 0000000000..2129c1926d --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/operators/uni/UniRunSubscribeOn_Instrumentation.java @@ -0,0 +1,21 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.operators.uni; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; +import io.smallrye.mutiny.subscription.UniSubscriber_Instrumentation; + +@Weave(originalName = "io.smallrye.mutiny.operators.uni.UniRunSubscribeOn", type = MatchType.ExactClass) +public class UniRunSubscribeOn_Instrumentation { + public void subscribe(UniSubscriber_Instrumentation subscriber) { + SmallRyeUtils.assignTokenToSubscriber(subscriber); + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/MultiSubscriber_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/MultiSubscriber_Instrumentation.java new file mode 100644 index 0000000000..3f1aac5683 --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/MultiSubscriber_Instrumentation.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.subscription; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; + +@Weave(originalName = "io.smallrye.mutiny.subscription.MultiSubscriber", type = MatchType.Interface) +public class MultiSubscriber_Instrumentation { + @Trace(async = true) + public void onItem(T item) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + token.link(); + } + + Weaver.callOriginal(); + } + + @Trace(async = true) + public void onCompletion() { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } + + @Trace(async = true) + public void onNext(T t) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + token.link(); + } + Weaver.callOriginal(); + + } + + @Trace(async = true) + public void onComplete() { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } + + @Trace(async = true) + public void onFailure(Throwable failure) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + NewRelic.noticeError(failure); + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } + + @Trace(async = true) + public void onError(Throwable t) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + NewRelic.noticeError(t); + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/UniSubscriber_Instrumentation.java b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/UniSubscriber_Instrumentation.java new file mode 100644 index 0000000000..9a1d11121c --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/main/java/io/smallrye/mutiny/subscription/UniSubscriber_Instrumentation.java @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.smallrye.mutiny.subscription; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.SmallRyeUtils; + +@Weave(originalName = "io.smallrye.mutiny.subscription.UniSubscriber", type = MatchType.Interface) +public class UniSubscriber_Instrumentation { + @Trace(async = true) + public void onFailure(Throwable t) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + NewRelic.noticeError(t); + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } + + @Trace(async = true) + public void onItem(T x) { + Token token = SmallRyeUtils.getToken(this); + if (token != null) { + token.linkAndExpire(); + SmallRyeUtils.removeToken(this); + } + + Weaver.callOriginal(); + } +} diff --git a/instrumentation/smallrye-mutiny-1.0.0/src/test/java/com/nr/agent/instrumentation/SmallRyeUtilsTest.java b/instrumentation/smallrye-mutiny-1.0.0/src/test/java/com/nr/agent/instrumentation/SmallRyeUtilsTest.java new file mode 100644 index 0000000000..ccd8174fd1 --- /dev/null +++ b/instrumentation/smallrye-mutiny-1.0.0/src/test/java/com/nr/agent/instrumentation/SmallRyeUtilsTest.java @@ -0,0 +1,126 @@ +/* + * + * * Copyright 2026 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation; + +import com.newrelic.agent.introspec.InstrumentationTestConfig; +import com.newrelic.agent.introspec.InstrumentationTestRunner; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = {"io.smallrye.mutiny"}) +public class SmallRyeUtilsTest { + @Test + public void getToken_withNoTokenStored_returnsNull() { + assertNull(SmallRyeUtils.getToken(new Object())); + } + + @Test + public void getToken_withStoredToken_returnsToken() { + Object key = new Object(); + Token token = NewRelic.getAgent().getTransaction().getToken(); + SmallRyeUtils.putToken(key, token); + assertSame(token, SmallRyeUtils.getToken(key)); + SmallRyeUtils.removeToken(key); + } + + @Test + public void putToken_withValidKeyAndToken_storesToken() { + Object key = new Object(); + Token token = NewRelic.getAgent().getTransaction().getToken(); + SmallRyeUtils.putToken(key, token); + assertSame(token, SmallRyeUtils.getToken(key)); + SmallRyeUtils.removeToken(key); + } + + @Test + public void putToken_calledTwiceWithSameKey_replacesToken() { + Object key = new Object(); + Token first = NewRelic.getAgent().getTransaction().getToken(); + Token second = NewRelic.getAgent().getTransaction().getToken(); + SmallRyeUtils.putToken(key, first); + SmallRyeUtils.putToken(key, second); + assertSame(second, SmallRyeUtils.getToken(key)); + SmallRyeUtils.removeToken(key); + } + + @Test + public void removeToken_withStoredToken_removesToken() { + Object key = new Object(); + SmallRyeUtils.putToken(key, NewRelic.getAgent().getTransaction().getToken()); + SmallRyeUtils.removeToken(key); + assertNull(SmallRyeUtils.getToken(key)); + } + + @Test + public void removeToken_withNoStoredToken_doesNotThrow() { + SmallRyeUtils.removeToken(new Object()); + } + + @Test + public void assignTokenToSubscriber_withNullSubscriber_doesNotThrow() { + SmallRyeUtils.assignTokenToSubscriber(null); + } + + @Test + public void assignTokenToSubscriber_withNoActiveTransaction_doesNotStoreToken() { + Object subscriber = new Object(); + SmallRyeUtils.assignTokenToSubscriber(subscriber); + assertNull(SmallRyeUtils.getToken(subscriber)); + } + + @Test + public void assignTokenToSubscriber_withActiveTransaction_storesToken() { + dispatcherForTest(); + } + + @Trace(dispatcher = true) + private void dispatcherForTest() { + Object subscriber = new Object(); + SmallRyeUtils.assignTokenToSubscriber(subscriber); + assertNotNull(SmallRyeUtils.getToken(subscriber)); + SmallRyeUtils.removeToken(subscriber); + } + + @Test + public void assignTokenToSubscriber_withExistingToken_doesNotReplaceExistingToken() { + dispatcherForTest2(); + } + + @Trace(dispatcher = true) + private void dispatcherForTest2() { + Object subscriber = new Object(); + Token existing = NewRelic.getAgent().getTransaction().getToken(); + SmallRyeUtils.putToken(subscriber, existing); + SmallRyeUtils.assignTokenToSubscriber(subscriber); + assertSame(existing, SmallRyeUtils.getToken(subscriber)); + SmallRyeUtils.removeToken(subscriber); + } + + @Test + public void assignTokenToSubscriber_calledTwice_doesNotReplaceTokenOnSecondCall() { + dispatcherForTest3(); + } + + @Trace(dispatcher = true) + private void dispatcherForTest3() { + Object subscriber = new Object(); + SmallRyeUtils.assignTokenToSubscriber(subscriber); + Token first = SmallRyeUtils.getToken(subscriber); + assertNotNull(first); + SmallRyeUtils.assignTokenToSubscriber(subscriber); + assertSame(first, SmallRyeUtils.getToken(subscriber)); + SmallRyeUtils.removeToken(subscriber); + } +} diff --git a/settings.gradle b/settings.gradle index fead58564f..b07e436891 100644 --- a/settings.gradle +++ b/settings.gradle @@ -351,6 +351,7 @@ include 'instrumentation:play-shaded-async-http-client-1.0.0' include 'instrumentation:play-ws-2.6.0' include 'instrumentation:play-ws-2.13_2.6.0' include 'instrumentation:portlet-2.0' +include 'instrumentation:quarkus-rest-3.0.0' include 'instrumentation:quartz-1.8.3' include 'instrumentation:quartz-2.0.0' include 'instrumentation:ning-async-http-client-1.0' @@ -394,6 +395,7 @@ include 'instrumentation:slick-3.0.0' include 'instrumentation:slick-2.11_3.2.0' include 'instrumentation:slick-2.12_3.2.0' include 'instrumentation:slick-2.12_3.5.0' +include 'instrumentation:smallrye-mutiny-1.0.0' include 'instrumentation:solr-5.1.0' include 'instrumentation:solr-5.4.0' include 'instrumentation:solr-6.4.0'