diff --git a/instrumentation/netty-reactor-0.8.0/NOTICE.txt b/instrumentation/netty-reactor-0.8.0/NOTICE.txt deleted file mode 100644 index 2a0270a9c2..0000000000 --- a/instrumentation/netty-reactor-0.8.0/NOTICE.txt +++ /dev/null @@ -1,17 +0,0 @@ -This product contains a modified part of OpenTelemetry: - - * License: - -Copyright 2019 The OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License -is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -or implied. See the License for the specific language governing permissions and limitations under -the License. - - * Homepage: https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/LICENSE diff --git a/instrumentation/netty-reactor-0.8.0/README.md b/instrumentation/netty-reactor-0.8.0/README.md deleted file mode 100644 index 6e8c9267be..0000000000 --- a/instrumentation/netty-reactor-0.8.0/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Reactor Netty Instrumentation - -Instrumentation for Reactor Netty server and also some widely used Reactor Core library code. - -This module is largely responsible for instrumenting the Reactor Core library to facilitate the passing, retrieval, -and linking of `Tokens` across contexts to tie asynchronous threads together for individual `Transactions`. - -This instrumentation is dependent on other instrumentation modules to start a `Transaction`. -Typically, the `netty-n.n` modules work with this instrumentation and will start a `Transaction` (see `NettyDispatcher#channelRead`). - -Most commonly this instrumentation comes into play with SpringBoot usage, in which case the `spring` and `spring-webflux` -instrumentation modules also apply and should result in `Transactions` being renamed after the Spring controller. - -## Key Components - -* `TokenLinkingSubscriber` - Implementation of a `reactor.core.CoreSubscriber` (a `Context` aware subscriber) that can be added as - a lifecycle hook on `Flux`/`Mono` operators to propagate, retrieve, and link `Tokens` across async contexts. This is done in various places as follows: - - ```java - if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { - Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift()); - } - ``` - -* `Schedulers_Instrumentation` and `HttpTrafficHandler_Instrumentation` - Both of these classes serve as entry points to add the `TokenLinkingSubscriber` sub-hook. - -* Scheduler `Task`s - Reactor Core Scheduler tasks that execute on asynchronous threads. These are instrumented as points to link `Tokens`. - -## Troubleshooting - -In cases where a `Transaction` gets named `/NettyDispatcher` (or named after a security `Filter`) it usually indicates that context was lost somewhere in -reactor code and that activity on threads where other instrumentation would typically apply could not be linked to the originating `Transaction` thread. -Figuring out how to propagate and link a `Token` across the threads should resolve the issue. diff --git a/instrumentation/netty-reactor-0.9.0/NOTICE.txt b/instrumentation/netty-reactor-0.9.0/NOTICE.txt deleted file mode 100644 index 2a0270a9c2..0000000000 --- a/instrumentation/netty-reactor-0.9.0/NOTICE.txt +++ /dev/null @@ -1,17 +0,0 @@ -This product contains a modified part of OpenTelemetry: - - * License: - -Copyright 2019 The OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License -is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -or implied. See the License for the specific language governing permissions and limitations under -the License. - - * Homepage: https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/LICENSE diff --git a/instrumentation/netty-reactor-0.9.0/README.md b/instrumentation/netty-reactor-0.9.0/README.md deleted file mode 100644 index 6e8c9267be..0000000000 --- a/instrumentation/netty-reactor-0.9.0/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Reactor Netty Instrumentation - -Instrumentation for Reactor Netty server and also some widely used Reactor Core library code. - -This module is largely responsible for instrumenting the Reactor Core library to facilitate the passing, retrieval, -and linking of `Tokens` across contexts to tie asynchronous threads together for individual `Transactions`. - -This instrumentation is dependent on other instrumentation modules to start a `Transaction`. -Typically, the `netty-n.n` modules work with this instrumentation and will start a `Transaction` (see `NettyDispatcher#channelRead`). - -Most commonly this instrumentation comes into play with SpringBoot usage, in which case the `spring` and `spring-webflux` -instrumentation modules also apply and should result in `Transactions` being renamed after the Spring controller. - -## Key Components - -* `TokenLinkingSubscriber` - Implementation of a `reactor.core.CoreSubscriber` (a `Context` aware subscriber) that can be added as - a lifecycle hook on `Flux`/`Mono` operators to propagate, retrieve, and link `Tokens` across async contexts. This is done in various places as follows: - - ```java - if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { - Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift()); - } - ``` - -* `Schedulers_Instrumentation` and `HttpTrafficHandler_Instrumentation` - Both of these classes serve as entry points to add the `TokenLinkingSubscriber` sub-hook. - -* Scheduler `Task`s - Reactor Core Scheduler tasks that execute on asynchronous threads. These are instrumented as points to link `Tokens`. - -## Troubleshooting - -In cases where a `Transaction` gets named `/NettyDispatcher` (or named after a security `Filter`) it usually indicates that context was lost somewhere in -reactor code and that activity on threads where other instrumentation would typically apply could not be linked to the originating `Transaction` thread. -Figuring out how to propagate and link a `Token` across the threads should resolve the issue. diff --git a/instrumentation/reactor-3.3.0/NOTICE.txt b/instrumentation/reactor-3.3.0/NOTICE.txt deleted file mode 100644 index 2a0270a9c2..0000000000 --- a/instrumentation/reactor-3.3.0/NOTICE.txt +++ /dev/null @@ -1,17 +0,0 @@ -This product contains a modified part of OpenTelemetry: - - * License: - -Copyright 2019 The OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License -is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -or implied. See the License for the specific language governing permissions and limitations under -the License. - - * Homepage: https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/LICENSE diff --git a/instrumentation/reactor-3.3.0/README.md b/instrumentation/reactor-3.3.0/README.md deleted file mode 100644 index 3c0a851b3f..0000000000 --- a/instrumentation/reactor-3.3.0/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Reactor Instrumentation - -Instrumentation for Reactor Core library code. This module provides mostly the same functionality of `netty-reactor-0.9.0`, but this will only apply when netty -is not being used. - -This instrumentation module is a subset of the `netty-reactor-0.9.0` instrumentation. It does not contain anything related to HTTP nor starting transactions and has added Skips for when `reactor-netty` classes are present. - -Changes to `netty-reactor` should be mirrored here and vice-versa. - -## Why not separate the functionality? -`netty-reactor` modules register the `tokenLift` in the Hooks class from two different code paths, one from reactor and another from netty. To make sure that -`tokenLift` is registered only once, an AtomicBoolean new field is added to the `Hooks` class. Before registering, that field is checked and if false, -`tokenLift` is registered and the field is set to true. - -The code cannot be separated in modules because the new field is not visible across modules, so there would be no way to make sure that `tokenLift` gets -registered only once. - -## Notice -This module will only properly link the code if the Mono/Flux is subscribed on a scheduler. - -Example: -``` -Flux.just(1, 2, 3) - .map(i -> doSomething(i)) - .subscribeOn(Schedulers.parallel()) - .subscribe(); -``` diff --git a/instrumentation/reactor-3.3.0/build.gradle b/instrumentation/reactor-3.3.0/build.gradle deleted file mode 100644 index bdd73f0d36..0000000000 --- a/instrumentation/reactor-3.3.0/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -dependencies { - implementation(project(":agent-bridge")) - implementation("io.projectreactor:reactor-core:3.3.21.RELEASE") -} - -jar { - manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-3.3.0' } -} - -verifyInstrumentation { - passesOnly 'io.projectreactor:reactor-core:[3.3.0.RELEASE,)' -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/ReactorConfig.java b/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/ReactorConfig.java deleted file mode 100644 index 0c57943f39..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/ReactorConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package com.nr.instrumentation; - -import com.newrelic.api.agent.NewRelic; - -public class ReactorConfig { - public static final boolean errorsEnabled = NewRelic.getAgent().getConfig() - .getValue("reactor.errors.enabled", false); - - private ReactorConfig() { - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/SubscriptionWrapper.java b/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/SubscriptionWrapper.java deleted file mode 100644 index 4d011a81f7..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/SubscriptionWrapper.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * * Copyright 2026 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package com.nr.instrumentation; - -import com.newrelic.api.agent.Token; -import org.reactivestreams.Subscription; -import reactor.util.context.Context; - -public class SubscriptionWrapper implements Subscription { - - Subscription delegate; - Context currentContext; - - public SubscriptionWrapper(Subscription delegate, Context context) { - this.delegate = delegate; - this.currentContext = context; - } - - @Override - public void request(long n) { - delegate.request(n); - } - - @Override - public void cancel() { - Token token = currentContext.getOrDefault("newrelic-token", null); - if (token != null) { - token.linkAndExpire(); - } - if (delegate != null) delegate.cancel(); - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/reactor/TokenLinkingSubscriber.java b/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/reactor/TokenLinkingSubscriber.java deleted file mode 100644 index af08ac44ce..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/com/nr/instrumentation/reactor/TokenLinkingSubscriber.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package com.nr.instrumentation.reactor; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.Trace; -import com.nr.instrumentation.ReactorConfig; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; -import reactor.core.Scannable; -import reactor.core.publisher.Operators; -import reactor.util.context.Context; - -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * Implementation of a reactor.core.CoreSubscriber (a Context aware subscriber) that can be added as - * a lifecycle hook on Flux/Mono operators to propagate, retrieve, and link Tokens across async contexts. - * - * Based on OpenTelemetry code: - * https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/TracingSubscriber.java - * @param - */ -public class TokenLinkingSubscriber implements CoreSubscriber { - private final Token token; - private final Subscriber subscriber; - private Context context; - - public TokenLinkingSubscriber(Subscriber subscriber, Context ctx) { - this.subscriber = subscriber; - this.context = ctx; - // newrelic-token is added by spring-webflux instrumentation of ServerWebExchange - this.token = ctx.getOrDefault("newrelic-token", null); - } - - @Override - public void onSubscribe(Subscription subscription) { - withNRToken(() -> subscriber.onSubscribe(subscription)); - } - - @Override - public void onNext(T o) { - withNRToken(() -> subscriber.onNext(o)); - } - - @Override - public void onError(Throwable throwable) { - withNRError(() -> subscriber.onError(throwable), throwable); - } - - @Override - public void onComplete() { - withNRToken(subscriber::onComplete); - } - - @Override - public Context currentContext() { - return context; - } - - @Trace(async = true, excludeFromTransactionTrace = true) - private void withNRToken(Runnable runnable) { - if (token != null && AgentBridge.getAgent().getTransaction(false) == null) { - token.link(); - } - runnable.run(); - } - - @Trace(async = true, excludeFromTransactionTrace = true) - private void withNRError(Runnable runnable, Throwable throwable) { - if (token != null && token.isActive()) { - token.link(); - if (ReactorConfig.errorsEnabled) { - NewRelic.noticeError(throwable); - } - } - runnable.run(); - } - - public static Function, ? extends Publisher> tokenLift() { - return Operators.lift(new TokenLifter<>()); - } - - private static class TokenLifter - implements BiFunction, CoreSubscriber> { - - public TokenLifter() { - } - - @Override - public CoreSubscriber apply(Scannable publisher, CoreSubscriber sub) { - // if Flux/Mono #just, #empty, #error - if (publisher instanceof Fuseable.ScalarCallable) { - return sub; - } - Token token = sub.currentContext().getOrDefault("newrelic-token", null); - if (token != null ) { - return new TokenLinkingSubscriber<>(sub, sub.currentContext()); - } - return sub; - } - } -} \ No newline at end of file diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/FluxMapFuseable_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/FluxMapFuseable_Instrumentation.java deleted file mode 100644 index 164d927fbd..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/FluxMapFuseable_Instrumentation.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * - * * Copyright 2024 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.publisher; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.weaver.MatchType; -import com.newrelic.api.agent.weaver.NewField; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.WeaveAllConstructors; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(type = MatchType.ExactClass, originalName = "reactor.core.publisher.FluxMapFuseable") -abstract class FluxMapFuseable_Instrumentation { - - @Weave(type = MatchType.ExactClass, originalName = "reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber") - static final class MapFuseableSubscriber_Instrumentation { - @NewField - private Token token; - - @WeaveAllConstructors - MapFuseableSubscriber_Instrumentation() { - if (AgentBridge.getAgent().getTransaction(false) != null && token == null) { - token = NewRelic.getAgent().getTransaction().getToken(); - } - } - - public void onComplete() { - if (token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - - public void onNext(T t) { - if (token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - - public void onError(Throwable t) { - if (token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - - public void cancel() { - if (token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/Hooks_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/Hooks_Instrumentation.java deleted file mode 100644 index e6f0b58502..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/Hooks_Instrumentation.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.publisher; - -import com.newrelic.api.agent.weaver.NewField; -import com.newrelic.api.agent.weaver.Weave; - -import java.util.concurrent.atomic.AtomicBoolean; - -@Weave(originalName = "reactor.core.publisher.Hooks") -public abstract class Hooks_Instrumentation { - - /* - * Note that sub-hooks are cumulative. We want to avoid setting the same sub-hooks - * more than once, so we set this boolean to true the first time we set a sub-hook. - * if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { Hooks.onEachOperator(...) } - */ - @NewField - public static AtomicBoolean instrumented = new AtomicBoolean(false); -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaMonoSubscriber_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaMonoSubscriber_Instrumentation.java deleted file mode 100644 index b83e609d63..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaMonoSubscriber_Instrumentation.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.publisher; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.weaver.NewField; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.WeaveAllConstructors; -import com.newrelic.api.agent.weaver.Weaver; -import com.nr.instrumentation.SubscriptionWrapper; -import org.reactivestreams.Subscription; -import reactor.util.context.Context; - -@Weave(originalName = "reactor.core.publisher.LambdaMonoSubscriber") -abstract class LambdaMonoSubscriber_Instrumentation { - @NewField - private Context nrContext; - final Context initialContext = Weaver.callOriginal(); - - @WeaveAllConstructors - protected LambdaMonoSubscriber_Instrumentation() { - // LamdaMonoSubscriber creates a new Context, so we create a new token and put it on the Context - // to be linked by TokenLinkingSubscriber but expired on onComplete here - if (AgentBridge.getAgent().getTransaction(false) != null - && initialContext.getOrDefault("newrelic-token", null) == null) { - nrContext = Context.of("newrelic-token", NewRelic.getAgent().getTransaction().getToken()); - } - } - - public final void onComplete() { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public final void onError(Throwable t) { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public void dispose() { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public final void onSubscribe(Subscription s) { - if (s != null) { - s = new SubscriptionWrapper(s, this.currentContext()); - } - Weaver.callOriginal(); - } - - public Context currentContext() { - if (nrContext != null) { - return initialContext.putAll(nrContext); - } - return Weaver.callOriginal(); - } - -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaSubscriber_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaSubscriber_Instrumentation.java deleted file mode 100644 index e136dc0b52..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/LambdaSubscriber_Instrumentation.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.publisher; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.weaver.NewField; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.WeaveAllConstructors; -import com.newrelic.api.agent.weaver.Weaver; -import com.nr.instrumentation.SubscriptionWrapper; -import org.reactivestreams.Subscription; -import reactor.util.context.Context; - -@Weave(originalName = "reactor.core.publisher.LambdaSubscriber") -abstract class LambdaSubscriber_Instrumentation { - final Context initialContext = Weaver.callOriginal(); - @NewField - private Context nrContext; - - @WeaveAllConstructors - protected LambdaSubscriber_Instrumentation() { - if (AgentBridge.getAgent().getTransaction(false) != null - && initialContext.getOrDefault("newrelic-token", null) == null) { - nrContext = Context.of("newrelic-token", NewRelic.getAgent().getTransaction().getToken()); - } - } - - public final void onComplete() { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public final void onError(Throwable t) { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public void dispose() { - Token token = this.currentContext().getOrDefault("newrelic-token", null); - if (token != null) { - token.expire(); - this.nrContext = null; - } - Weaver.callOriginal(); - } - - public final void onSubscribe(Subscription s) { - if (s != null) { - s = new SubscriptionWrapper(s, this.currentContext()); - } - Weaver.callOriginal(); - } - - public Context currentContext() { - if (nrContext != null) { - //return nrContext; - return initialContext.putAll(nrContext); - } - return Weaver.callOriginal(); - } - -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/MonoSubscribeOn_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/MonoSubscribeOn_Instrumentation.java deleted file mode 100644 index 92dd50ef4b..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/publisher/MonoSubscribeOn_Instrumentation.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * - * * Copyright 2024 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.publisher; - -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.weaver.MatchType; -import com.newrelic.api.agent.weaver.NewField; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.WeaveAllConstructors; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(type = MatchType.ExactClass, originalName = "reactor.core.publisher.MonoSubscribeOn") -abstract class MonoSubscribeOn_Instrumentation { - - @Weave(type = MatchType.ExactClass, originalName = "reactor.core.publisher.MonoSubscribeOn$SubscribeOnSubscriber") - static final class SubscribeOnSubscriber_Instrumentation { - @NewField - private Token token; - - @WeaveAllConstructors - SubscribeOnSubscriber_Instrumentation() { - if (NewRelic.getAgent().getTransaction() != null && token == null) { - token = NewRelic.getAgent().getTransaction().getToken(); - } - } - - public void run () { - if (token != null) { - Boolean result = token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - - public void cancel () { - if (token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/InstantPeriodicWorkerTask_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/InstantPeriodicWorkerTask_Instrumentation.java deleted file mode 100644 index 4ee5d5c532..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/InstantPeriodicWorkerTask_Instrumentation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.scheduler; - -import com.newrelic.api.agent.Trace; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(originalName = "reactor.core.scheduler.InstantPeriodicWorkerTask") -final class InstantPeriodicWorkerTask_Instrumentation { - - // We need to be able to link the Token here when executing on a supplied Scheduler - // A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator - @Trace(async = true, excludeFromTransactionTrace = true) - public Void call() { - return Weaver.callOriginal(); - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/PeriodicWorkerTask_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/PeriodicWorkerTask_Instrumentation.java deleted file mode 100644 index ad01576c16..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/PeriodicWorkerTask_Instrumentation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.scheduler; - -import com.newrelic.api.agent.Trace; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(originalName = "reactor.core.scheduler.PeriodicWorkerTask") -final class PeriodicWorkerTask_Instrumentation { - - // We need to be able to link the Token here when executing on a supplied Scheduler - // A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator - @Trace(async = true, excludeFromTransactionTrace = true) - public Void call() { - return Weaver.callOriginal(); - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/SchedulerTask_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/SchedulerTask_Instrumentation.java deleted file mode 100644 index af818096b6..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/SchedulerTask_Instrumentation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.scheduler; - -import com.newrelic.api.agent.Trace; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(originalName = "reactor.core.scheduler.SchedulerTask") -final class SchedulerTask_Instrumentation { - - // We need to be able to link the Token here when executing on a supplied Scheduler via Mono::publishOn - // A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator - @Trace(async = true, excludeFromTransactionTrace = true) - public Void call() { - return Weaver.callOriginal(); - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java deleted file mode 100644 index 9e7a1bbf0e..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package reactor.core.scheduler; - -import com.newrelic.api.agent.weaver.MatchType; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.Weaver; -import com.nr.instrumentation.reactor.TokenLinkingSubscriber; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Hooks_Instrumentation; - -import static com.nr.instrumentation.reactor.TokenLinkingSubscriber.tokenLift; - -@Weave(type = MatchType.BaseClass, originalName = "reactor.core.scheduler.Schedulers") -public abstract class Schedulers_Instrumentation { - - @Weave(type = MatchType.ExactClass, originalName = "reactor.core.scheduler.Schedulers$CachedScheduler") - static class CachedScheduler { - CachedScheduler(String key, Scheduler cached) { - /* - * Add tokenLift hook if it hasn't already been added. This allows for tokens to be retrieved from - * the current context and linked across threads at various points of the Flux/Mono lifecycle. - * - * When using Netty Reactor with SpringBoot this hook will be added by the HttpTrafficHandler_Instrumentation - * but when using other embedded web servers (e.g. Tomcat, Jetty, Undertow) the HttpTrafficHandler class - * doesn't get loaded and thus the hook isn't added. This ensures that the hook is added in a common code - * path before any Scheduler Tasks are spun off on new threads. - */ - if (!Hooks_Instrumentation.instrumented.getAndSet(true)) { - Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift()); - } - } - } - -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/WorkerTask_Instrumentation.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/WorkerTask_Instrumentation.java deleted file mode 100644 index 2e51fdaf22..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/core/scheduler/WorkerTask_Instrumentation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.core.scheduler; - -import com.newrelic.api.agent.Trace; -import com.newrelic.api.agent.weaver.Weave; -import com.newrelic.api.agent.weaver.Weaver; - -@Weave(originalName = "reactor.core.scheduler.WorkerTask") -final class WorkerTask_Instrumentation { - - // We need to be able to link the Token here when executing on a supplied Scheduler - // A Token should be available on the thread that this task executes on if tokenLift() was added to Hooks.onEachOperator - @Trace(async = true, excludeFromTransactionTrace = true) - public Void call() { - return Weaver.callOriginal(); - } -} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/ipc/netty/http/server/HttpServerHandler_Skip.java b/instrumentation/reactor-3.3.0/src/main/java/reactor/ipc/netty/http/server/HttpServerHandler_Skip.java deleted file mode 100644 index 314f8b7432..0000000000 --- a/instrumentation/reactor-3.3.0/src/main/java/reactor/ipc/netty/http/server/HttpServerHandler_Skip.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * - * * Copyright 2023 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package reactor.ipc.netty.http.server; - -import com.newrelic.api.agent.weaver.SkipIfPresent; - -// prevents this module from applying when reactor-netty 0.7.0 is present -@SkipIfPresent(originalName = "reactor.ipc.netty.http.server.HttpServerHandler") -class HttpServerHandler_Skip { -} diff --git a/instrumentation/reactor-3.3.0/src/test/java/com/nr/agent/instrumentation/TransactionPropagationTest.java b/instrumentation/reactor-3.3.0/src/test/java/com/nr/agent/instrumentation/TransactionPropagationTest.java deleted file mode 100644 index ec0aff4806..0000000000 --- a/instrumentation/reactor-3.3.0/src/test/java/com/nr/agent/instrumentation/TransactionPropagationTest.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * - * * Copyright 2023 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.agent.bridge.Token; -import com.newrelic.agent.introspec.InstrumentationTestConfig; -import com.newrelic.agent.introspec.InstrumentationTestRunner; -import com.newrelic.agent.introspec.Introspector; -import com.newrelic.api.agent.Trace; -import com.nr.instrumentation.reactor.TokenLinkingSubscriber; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.util.context.Context; - -import java.time.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -import static com.nr.instrumentation.reactor.TokenLinkingSubscriber.tokenLift; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertTrue; - -@SuppressWarnings("deprecation") -@RunWith(InstrumentationTestRunner.class) -@InstrumentationTestConfig(includePrefixes = {"reactor"}) -public class TransactionPropagationTest { - - public static final String A_VALUE = ""; - - @BeforeClass - public static void init() { - Hooks.onEachOperator(TokenLinkingSubscriber.class.getName(), tokenLift()); - } - - @Test - public void syncPropagationSanityCheck() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> - checkTransaction(hadTransaction)); - assertCapturedData(hadTransaction); - } - - @Test - public void asyncPropagationSanityCheck() throws InterruptedException { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - inAnotherThread(() -> - inAnnotatedWithTraceAsync(() -> { - token.linkAndExpire(); - checkTransaction(hadTransaction); - done.countDown(); - }) - ); - }); - done.await(); - assertCapturedData(hadTransaction); - } - - @Test - public void testReactorSchedulersInstrumentation() throws InterruptedException { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - Schedulers.elastic().schedule(() -> { -// trace_async(() -> { it is not need as Tasks are instrumented and annotated with @Trace(async = ture) - token.linkAndExpire(); - checkTransaction(hadTransaction); - done.countDown(); -// }); - }); - }); - done.await(); - assertCapturedData(hadTransaction); - } - - @Test - public void testEmptyMonoOnSuccess() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - Mono.empty() - .subscribeOn(Schedulers.elastic()) - .doOnSuccess(v -> - checkTransaction(hadTransaction)) - .subscriberContext(with(token)) - .block(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test - public void testEmptyFluxOnComplete() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - Flux.empty() - .subscribeOn(Schedulers.elastic()) - .doOnComplete(() -> - checkTransaction(hadTransaction)) - .subscriberContext(with(token)) - .blockFirst(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test - public void testMonoOnSuccess() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - Mono.just(A_VALUE) - .subscribeOn(Schedulers.elastic()) - .doOnSuccess(v -> - checkTransaction(hadTransaction)) - .subscriberContext(with(token)) - .block(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test - public void testMonoRetryOnSuccess() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - AtomicBoolean firstCall = new AtomicBoolean(true); - Mono - .create(monoSink -> - inAnotherThread(() -> { - if (firstCall.getAndSet(false)) - monoSink.error(new RuntimeException("failing the first call")); - else - monoSink.success(A_VALUE); - }) - ) - .doOnSuccess(v -> - checkTransaction(hadTransaction)) - .retry(2) - .subscriberContext(with(token)) - .block(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test - public void testMonoRetryBackoffOnSuccess() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - AtomicBoolean firstCall = new AtomicBoolean(true); - Mono - .create(monoSink -> - inAnotherThread(() -> { - if (firstCall.getAndSet(false)) - monoSink.error(new RuntimeException("failing the first call")); - else - monoSink.success(A_VALUE); - }) - ) - .doOnSuccess(v -> - checkTransaction(hadTransaction)) - .retryBackoff(2, Duration.ZERO) - .subscriberContext(with(token)) - .block(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test - public void testMonoNestedInFlatMap() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - inTransaction(() -> { - Token token = createToken(); - Mono.just(A_VALUE) - .subscribeOn(Schedulers.elastic()) - .flatMap(v -> - Mono.just(A_VALUE) - .subscribeOn(Schedulers.elastic()) - .doOnSuccess(v2 -> - checkTransaction(hadTransaction))) - .subscriberContext(with(token)) - .block(); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test(timeout = 10000L) - public void testLambdaMonoSubscriberOnSuccess() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - Mono.empty() - .subscribeOn(Schedulers.elastic()) - .doOnSuccess(v -> - checkTransaction(hadTransaction)) - - // it is not need as LambdaMonoSubscriber instrumentation creates token - // and puts it into the context - //.subscriberContext(with(token)) - - // Call countDown in onComplete to see that instrumentation code calls original method - .subscribe(nil(), nil(), done::countDown); - await(done); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test(timeout = 10000L) - public void testLambdaMonoSubscriberOnError() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - Mono.error(new RuntimeException()) - .subscribeOn(Schedulers.elastic()) - .doOnError(v -> - checkTransaction(hadTransaction)) - - // it is not need as LambdaMonoSubscriber instrumentation creates token - // and puts it into the context - //.subscriberContext(with(token)) - - // Call countDown in onError to see that instrumentation code calls original method - .subscribe(nil(), v -> done.countDown()); - await(done); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test(timeout = 10000L) - public void testLambdaSubscriberOnComplete() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - Flux.empty() - .subscribeOn(Schedulers.elastic()) - .doOnComplete(() -> - checkTransaction(hadTransaction)) - - // it is not need as LambdaSubscriber instrumentation creates token - // and puts it into the context - //.subscriberContext(with(token)) - - // Call countDown in onComplete to see that instrumentation code calls original method - .subscribe(nil(), nil(), done::countDown); - await(done); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Test(timeout = 10000L) - public void testLambdaSubscriberOnError() { - AtomicBoolean hadTransaction = new AtomicBoolean(); - CountDownLatch done = new CountDownLatch(1); - inTransaction(() -> { - Token token = createToken(); - Flux.error(new RuntimeException()) - .subscribeOn(Schedulers.elastic()) - .doOnError(v -> - checkTransaction(hadTransaction)) - - // it is not need as LambdaSubscriber instrumentation creates token - // and puts it into the context - //.subscriberContext(with(token)) - - // Call countDown in onError to see that instrumentation code calls original method - .subscribe(nil(), v -> done.countDown()); - await(done); - token.expire(); - }); - assertCapturedData(hadTransaction); - } - - @Trace(dispatcher = true) - public void inTransaction(Runnable actions) { - actions.run(); - } - - public void inAnotherThread(Runnable runnable) { - new Thread(runnable).start(); - } - - @Trace(async = true) - public void inAnnotatedWithTraceAsync(Runnable runnable) { - runnable.run(); - } - - public Token createToken() { - return AgentBridge.getAgent().getTransaction(false).getToken(); - } - - public Context with(Token token) { - return Context.empty().put("newrelic-token", token); - } - - @Trace - public void checkTransaction(AtomicBoolean hadTransaction) { - hadTransaction.set(AgentBridge.getAgent().getTransaction(false) != null); - } - - private Consumer nil() { - return v -> { - }; - } - - private void await(CountDownLatch done) { - try { - done.await(); - } catch (InterruptedException ignore) { - } - } - - private void assertCapturedData(AtomicBoolean hadTransaction) { - assertTrue("Did not have transaction", hadTransaction.get()); - - Introspector introspector = InstrumentationTestRunner.getIntrospector(); - - assertThat("No finished transactions", introspector.getFinishedTransactionCount(), - is(greaterThan(0))); - - assertThat("Transaction names", introspector.getTransactionNames(), contains( - "OtherTransaction/Custom/" + getClass().getName() + "/inTransaction" - )); - - assertThat("Unscoped metrics", introspector.getUnscopedMetrics().keySet(), hasItems( - "Java/" + getClass().getName() + "/inTransaction", - "Custom/" + getClass().getName() + "/checkTransaction" - )); - } -} diff --git a/instrumentation/reactor-core-3.1.0/build.gradle b/instrumentation/reactor-core-3.1.0/build.gradle new file mode 100644 index 0000000000..1b20f6f987 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("io.projectreactor:reactor-core:3.1.0.RELEASE") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-core-3.1.0' } +} + +verifyInstrumentation { + passesOnly 'io.projectreactor:reactor-core:[3.1.0.RELEASE,3.3.0.RELEASE)' +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java new file mode 100644 index 0000000000..6fbcb1db51 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnableWrapper implements Runnable { + + private Runnable delegate = null; + + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnableWrapper(Runnable r, Token t) { + delegate = r; + token = t; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + @Trace(async=true, excludeFromTransactionTrace = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java new file mode 100644 index 0000000000..ebca10190e --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java @@ -0,0 +1,12 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; + +public class ReactorConfig { + public static final boolean errorsEnabledNetty = NewRelic.getAgent().getConfig().getValue("reactor-netty.errors.enabled", false); + public static final boolean errorsRectorEnabled = NewRelic.getAgent().getConfig().getValue("reactor.errors.enabled", false); + public static final boolean errorsEnabled = errorsRectorEnabled || errorsEnabledNetty; + + private ReactorConfig() { + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java new file mode 100644 index 0000000000..8538825ebe --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java @@ -0,0 +1,25 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class ReactorUtils { + + public static NRRunnableWrapper getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnableWrapper) {return null;} + + Token currentToken = NewRelic.getAgent().getTransaction().getToken(); + if(currentToken != null) { + if(currentToken.isActive()) { + return new NRRunnableWrapper(r, currentToken); + } else { + currentToken.expire(); + currentToken = null; + return null; + } + } + + return null; + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java new file mode 100644 index 0000000000..b6c1d1f337 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.BaseSubscriber") +public class BaseSubscriber_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java new file mode 100644 index 0000000000..729236d716 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.DirectProcessor") +public class DirectProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java new file mode 100644 index 0000000000..df30580ee9 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EmitterProcessor") +public class EmitterProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java new file mode 100644 index 0000000000..e3d4644c38 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EventLoopProcessor") +class EventLoopProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java new file mode 100644 index 0000000000..d7e22e025f --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java @@ -0,0 +1,120 @@ +package reactor.core.publisher; + +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.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.FluxCreate") +class FluxCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BaseSink", type = MatchType.BaseClass) + static abstract class BaseSink_Instrumentation implements FluxSink { + + @NewField + protected Token token = null; + + BaseSink_Instrumentation(CoreSubscriber actual) { + if(token == null) { + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null) { + if(!t.isActive()) { + token = t; + } else { + t.expire(); + t = null; + } + } + } + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BufferAsyncSink") + static abstract class BufferAsyncSink_Instrumentation extends BaseSink_Instrumentation { + BufferAsyncSink_Instrumentation(CoreSubscriber actual, int capacityHint) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$IgnoreSink") + static abstract class IgnoreSink_Instrumentation extends BaseSink_Instrumentation { + IgnoreSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$LatestAsyncSink") + static abstract class LatestAsyncSink_Instrumentation extends BaseSink_Instrumentation { + LatestAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$NoOverflowBaseAsyncSink") + static abstract class NoOverflowBaseAsyncSink_Instrumentation extends BaseSink_Instrumentation { + NoOverflowBaseAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java new file mode 100644 index 0000000000..fcc76ee126 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java @@ -0,0 +1,71 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.MonoCreate") +class MonoCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.MonoCreate$DefaultMonoSink") + static class DefaultMonoSink_Instrumentation { + + @NewField + private Token token = null; + + DefaultMonoSink_Instrumentation(CoreSubscriber actual) { + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null && t.isActive()) { + token = t; + } else if(t != null) { + t.expire(); + t = null; + } + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void success() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void success(T value) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void cancel() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java new file mode 100644 index 0000000000..cde9ad26a2 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.MonoProcessor") +public class MonoProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java new file mode 100644 index 0000000000..2eebc2c253 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastProcessor") +public class UnicastProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java new file mode 100644 index 0000000000..7b548210b3 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java @@ -0,0 +1,51 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; +import reactor.core.Disposable; + +import java.util.concurrent.TimeUnit; + +@Weave(originalName = "reactor.core.scheduler.Scheduler", type = MatchType.Interface) +public class Scheduler_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + @Weave(originalName = "reactor.core.scheduler.Scheduler$Worker", type = MatchType.Interface) + public static class Worker_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java new file mode 100644 index 0000000000..7ab5b3bcfe --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java @@ -0,0 +1,44 @@ +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; + +import reactor.core.Disposable; + +@Weave(originalName = "reactor.core.scheduler.Schedulers") +public class Schedulers_Instrumentation { + + @Trace + static Disposable directSchedule(ScheduledExecutorService exec, Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } + + public static Scheduler single() { + return Weaver.callOriginal(); + } + + @Trace + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-3.3.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java b/instrumentation/reactor-core-3.1.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java similarity index 100% rename from instrumentation/reactor-3.3.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java rename to instrumentation/reactor-core-3.1.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java new file mode 100644 index 0000000000..394d4e5651 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java @@ -0,0 +1,39 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitMany { + + private List result = null; + private CompletableFuture> f; + + public AwaitMany() { + f = new CompletableFuture<>(); + } + + public List await() { + List s = Collections.emptyList(); + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void onError(Throwable t) { + result.add(t.getMessage()); + f.completeExceptionally(t); + } + + public void done(List result) { + System.out.println("AwaitMany done"); + this.result = result; + f.complete(result); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java new file mode 100644 index 0000000000..44434be273 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java @@ -0,0 +1,32 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitSingle { + + private String result = null; + private CompletableFuture f; + + public AwaitSingle() { + f = new CompletableFuture(); + } + + public String getResult() { + return result; + } + + public String await() { + String s = null; + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void setResult(String s) { + result = s; + f.complete(result); + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java new file mode 100644 index 0000000000..ceddfe3b71 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor.test; + +import org.reactivestreams.Subscription; + +import com.newrelic.api.agent.Trace; + +import reactor.core.CoreSubscriber; + +public class MonoCoreSubscriber implements CoreSubscriber { + + @Override + @Trace + public void onNext(String t) { + System.out.println("Received string for onNext: " + t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Received error for onError: " + t); + } + + @Override + @Trace + public void onComplete() { + System.out.println("Mono has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription var1) { + System.out.println("Mono was subscribed to by : " + var1); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java new file mode 100644 index 0000000000..b3a2dce717 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java @@ -0,0 +1,23 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriptionConsumer implements Consumer { + + @Override + @Trace + public void accept(Subscription subscription) { + pause(); + + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java new file mode 100644 index 0000000000..39f17d4101 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java @@ -0,0 +1,377 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.agent.introspec.*; +import com.newrelic.api.agent.Trace; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = "reactor.core") +public class TestApplication { + + private static final String txn1 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoSub"; + private static final String txn2 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoPub"; + private static final String txn3 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxSub"; + private static final String txn4 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxPub"; + private static final String WRAPPER = "Java/com.nr.instrumentation.reactor.NRRunnableWrapper/run"; + private static final String MONO_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onNext"; + private static final String MONO_COMPLETE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onComplete"; + private static final String MONO_SUBSCRIBE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onSubscribe"; + private static final String[] fluxArray = {"Message 1", "Message 2", "Message 3", "Message 4"}; + + @Test + public void doMonoSubscribeOnTest() { + testMonoSub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transactions", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn1); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn1); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn1); + System.out.println("Transaction traces: " + traces.size()); + for (TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the subscribing occurs on another thread + */ + for (TraceSegment child : children) { + if (child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if (initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if (attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + break; + } + } + Assert.assertTrue(passes); + + } + } + + @Test + public void doMonoPublishOnTest() { + testMonoPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn2); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn2); + System.out.println("Transaction traces: " + traces.size()); + for(TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the publishing occurs on another thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if(initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if(attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + } + } + Assert.assertTrue(passes); + + } + + } + + @Test + public void doMonoSinkTest() { + monoSinkTest(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + // indicates that execution went to another thread + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + } + + @Trace(dispatcher = true) + public void monoSinkTest() { + Mono delayedMono = Mono.create(sink -> { + // Schedule an operation to emit a value after a delay + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + System.out.println("Emitting value from MonoSink..."); + sink.success("Hello from MonoSink!"); // Emit the value and complete + scheduler.dispose(); + }, 500, TimeUnit.MILLISECONDS); // Delay for 500 MS + + // Optional: Handle cancellation + sink.onDispose(() -> { + System.out.println("MonoSink disposed (e.g., subscriber cancelled)"); + scheduler.dispose(); + }); + }); + + AwaitSingle awaitSingle = new AwaitSingle(); + delayedMono.subscribe(new TestMonoCoreSubscriber(awaitSingle)); + String result = awaitSingle.await(); + System.out.println("Result of MonoSink is " + result); + + } + + private void printSegments(TraceSegment segment, int indents) { + String segmentName = segment.getName(); + String classname = segment.getClassName(); + String methodName = segment.getMethodName(); + int callCount = segment.getCallCount(); + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < indents; i++) { + sb.append(" "); + } + sb.append("Name: ").append(segmentName); + sb.append(", Class: ").append(classname); + sb.append(", Method: ").append(methodName); + sb.append(", CallCount: ").append(callCount); + Map attributes = segment.getTracerAttributes(); + if(attributes != null) { + Set keys = attributes.keySet(); + for(String key : keys) { + Object value = attributes.get(key); + sb.append(", Attribute: ").append(key); + sb.append(": "); + sb.append(value); + } + } + System.out.println(sb.toString()); + List children = segment.getChildren(); + for(TraceSegment child : children) { + printSegments(child, indents + 2); + } + } + + @Test + public void doFluxPublishOnTest() { + testFluxPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn4); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn4); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext")); + TracedMetricData metricData = metrics.get("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"); + Assert.assertNotNull(metricData); + Assert.assertEquals(4, metricData.getCallCount()); + + } + + @Test + public void doFluxSubscribeOnTest() { + testFluxSub(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn3); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn3); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + } + + @Test + public void doScheduleTest() { + testScheduler(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + String txnName = introspector.getTransactionNames().iterator().next(); + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Collection traces = introspector.getTransactionTracesForTransaction(txnName); + TransactionTrace transactionTrace = traces.iterator().next(); + Assert.assertNotNull(transactionTrace); + TraceSegment initial = transactionTrace.getInitialTraceSegment(); + Assert.assertNotNull(initial); + List children = initial.getChildren(); + boolean passes = false; + /* + Ensure that execution is dispatched to another thread and that Mono actions occur on that thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + List wrapperChildren = child.getChildren(); + Set childNames = new HashSet<>(); + for(TraceSegment wrapperChild : wrapperChildren) { + childNames.add(wrapperChild.getName()); + } + Assert.assertTrue(childNames.contains(MONO_SUBSCRIBE)); + Assert.assertTrue(childNames.contains(MONO_NEXT)); + Assert.assertTrue(childNames.contains(MONO_COMPLETE)); + } + } + } + + + @Trace(dispatcher = true) + public void testScheduler() { + System.out.println("Enter testScheduler"); + Mono mono = getStringMono(); + AwaitSingle await = new AwaitSingle(); + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + mono.subscribe(new TestMonoCoreSubscriber(await)); + }); + String result = await.await(); + System.out.println("TestScheduler result: " + result); + + } + + @Trace(dispatcher = true) + public void testMonoPub() { + System.out.println("Enter testMonoPub"); + AwaitSingle await = new AwaitSingle(); + Mono mono = getStringMono(); + + mono.publishOn(Schedulers.single()).subscribe(new TestMonoCoreSubscriber(await)); + await.await(); + + System.out.println("Exit testMonoPub with result: " + await.getResult()); + + } + + @Trace(dispatcher = true) + public void testMonoSub() { + System.out.println("Enter testMonoSub"); + AwaitSingle await = new AwaitSingle(); + + Mono mono = getStringMono().subscribeOn(Schedulers.single()).doOnSubscribe(new SubscriptionConsumer()); + + mono.subscribe(new TestMonoCoreSubscriber(await)); + String result = await.await(); + System.out.println("Exit testMonoSub with result: " + result); + + } + + @Trace(dispatcher = true) + public void testFluxSub() { + System.out.println("Enter testFluxSub"); + + Flux flux = getStringFlux().subscribeOn(Schedulers.single()); + + flux.subscribe(this::doSubscribeAction); + + List list = flux.collectList().block(); + System.out.println("Exit testFluxSub with result: " + list); + } + + @Trace(dispatcher = true) + public void testFluxPub() { + System.out.println("Enter testFluxPub"); + Flux flux = getStringFlux().publishOn(Schedulers.single()); + AwaitMany await = new AwaitMany(); + + flux.subscribe(new TestFluxCoreSubscriber(await,4)); + + List list = await.await(); + System.out.println("Exit testFluxPub with result: "+ list); + + } + + + + public Flux getStringFlux() { + return Flux.fromArray(fluxArray); + } + + public Mono getStringMono() { + + + return Mono.fromCallable(() -> { + try { + Thread.sleep(100L); + } catch(Exception ignored) { + + } + return "hello"; + }); + } + + @Trace + public void doSubscribeAction(String s) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Result is "+s); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java new file mode 100644 index 0000000000..0d87eb60d0 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java @@ -0,0 +1,52 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +import java.util.ArrayList; +import java.util.List; + +public class TestFluxCoreSubscriber implements CoreSubscriber { + + private AwaitMany await = null; + List result = null; + private int numberOfItems = 0; + public TestFluxCoreSubscriber(AwaitMany a, int numberOfItems) { + result = new ArrayList<>(); + await = a; + this.numberOfItems = numberOfItems; + } + + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result.add(t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: " + t.getMessage()); + if (await != null) { + List result = new ArrayList<>(); + result.add(t.getMessage()); + await.done(result); + } + + } + + @Override + @Trace + public void onComplete() { + await.done(result); + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(numberOfItems); + } + +} diff --git a/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java new file mode 100644 index 0000000000..0648030287 --- /dev/null +++ b/instrumentation/reactor-core-3.1.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java @@ -0,0 +1,50 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class TestMonoCoreSubscriber implements CoreSubscriber { + + private AwaitSingle await = null; + String result = null; + + public TestMonoCoreSubscriber(AwaitSingle a) { + await = a; + } + + @Override + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result = t; + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: "+t.getMessage()); + if(await != null) { + await.setResult(t.getMessage()); + } + + } + + @Override + @Trace + public void onComplete() { + if(await != null) { + synchronized(await) { + await.setResult(result); + } + } + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(1); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/build.gradle b/instrumentation/reactor-core-3.3.0/build.gradle new file mode 100644 index 0000000000..1d8d6a25b7 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("io.projectreactor:reactor-core:3.3.0.RELEASE") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-core-3.3.0' } +} + +verifyInstrumentation { + passesOnly 'io.projectreactor:reactor-core:[3.3.0.RELEASE,3.4.0)' +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java new file mode 100644 index 0000000000..6fbcb1db51 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnableWrapper implements Runnable { + + private Runnable delegate = null; + + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnableWrapper(Runnable r, Token t) { + delegate = r; + token = t; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + @Trace(async=true, excludeFromTransactionTrace = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java new file mode 100644 index 0000000000..d90133121f --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java @@ -0,0 +1,14 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; + +public class ReactorConfig { + + public static final boolean errorsEnabledNetty = NewRelic.getAgent().getConfig().getValue("reactor-netty.errors.enabled", false); + public static final boolean errorsRectorEnabled = NewRelic.getAgent().getConfig().getValue("reactor.errors.enabled", false); + public static final boolean errorsEnabled = errorsRectorEnabled || errorsEnabledNetty; + + private ReactorConfig() { + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java new file mode 100644 index 0000000000..8538825ebe --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java @@ -0,0 +1,25 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class ReactorUtils { + + public static NRRunnableWrapper getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnableWrapper) {return null;} + + Token currentToken = NewRelic.getAgent().getTransaction().getToken(); + if(currentToken != null) { + if(currentToken.isActive()) { + return new NRRunnableWrapper(r, currentToken); + } else { + currentToken.expire(); + currentToken = null; + return null; + } + } + + return null; + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java new file mode 100644 index 0000000000..b6c1d1f337 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.BaseSubscriber") +public class BaseSubscriber_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java new file mode 100644 index 0000000000..729236d716 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.DirectProcessor") +public class DirectProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java new file mode 100644 index 0000000000..df30580ee9 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EmitterProcessor") +public class EmitterProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java new file mode 100644 index 0000000000..e3d4644c38 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/EventLoopProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EventLoopProcessor") +class EventLoopProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java new file mode 100644 index 0000000000..d5ba761c06 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java @@ -0,0 +1,200 @@ +package reactor.core.publisher; + +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.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.FluxCreate") +class FluxCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BaseSink", type = MatchType.BaseClass) + static abstract class BaseSink_Instrumentation implements FluxSink { + + @NewField + protected Token token = null; + + BaseSink_Instrumentation(CoreSubscriber actual) { + if(token == null) { + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null) { + if(!t.isActive()) { + token = t; + } else { + t.expire(); + t = null; + } + } + } + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void complete() { + + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BufferAsyncSink") + static abstract class BufferAsyncSink_Instrumentation extends BaseSink_Instrumentation { + BufferAsyncSink_Instrumentation(CoreSubscriber actual, int capacityHint) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$IgnoreSink") + static abstract class IgnoreSink_Instrumentation extends BaseSink_Instrumentation { + IgnoreSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$LatestAsyncSink") + static abstract class LatestAsyncSink_Instrumentation extends BaseSink_Instrumentation { + LatestAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$NoOverflowBaseAsyncSink", type = MatchType.BaseClass) + static abstract class NoOverflowBaseAsyncSink_Instrumentation extends BaseSink_Instrumentation { + NoOverflowBaseAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if(token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializeOnRequestSink") + static class SerializeOnRequestSink_Instrumentation { + + @NewField + protected Token token; + + SerializeOnRequestSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + token = sink.token; + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializedSink") + static class SerializedSink_Instrumentation { + + @NewField + protected Token token; + + SerializedSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + token = sink.token; + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java new file mode 100644 index 0000000000..ac7f41a5bc --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java @@ -0,0 +1,72 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +@Weave(originalName = "reactor.core.publisher.MonoCreate") +class MonoCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.MonoCreate$DefaultMonoSink") + static class DefaultMonoSink_Instrumentation { + + @NewField + private Token token = null; + + DefaultMonoSink_Instrumentation(CoreSubscriber actual) { + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null && t.isActive()) { + token = t; + } else if(t != null) { + t.expire(); + t = null; + } + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void success() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void success(T value) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async=true, excludeFromTransactionTrace = true) + public void cancel() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java new file mode 100644 index 0000000000..cde9ad26a2 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/MonoProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.MonoProcessor") +public class MonoProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java new file mode 100644 index 0000000000..2eebc2c253 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java @@ -0,0 +1,18 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastProcessor") +public class UnicastProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java new file mode 100644 index 0000000000..ac922fbc8e --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java @@ -0,0 +1,7 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +@SkipIfPresent(originalName = "reactor.core.scheduler.ElasticScheduler$DirectScheduleTask") +public class DirectScheduleTask_Skip { +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java new file mode 100644 index 0000000000..7b548210b3 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java @@ -0,0 +1,51 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; +import reactor.core.Disposable; + +import java.util.concurrent.TimeUnit; + +@Weave(originalName = "reactor.core.scheduler.Scheduler", type = MatchType.Interface) +public class Scheduler_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + @Weave(originalName = "reactor.core.scheduler.Scheduler$Worker", type = MatchType.Interface) + public static class Worker_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java new file mode 100644 index 0000000000..2a4b38ae54 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java @@ -0,0 +1,45 @@ +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; + +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +@Weave(originalName = "reactor.core.scheduler.Schedulers") +public class Schedulers_Instrumentation { + + @Trace + static Disposable directSchedule(ScheduledExecutorService exec, Runnable task, Disposable parent, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } + + public static Scheduler single() { + return Weaver.callOriginal(); + } + + @Trace + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java new file mode 100644 index 0000000000..4831e7c131 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java @@ -0,0 +1,15 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package reactor.netty.http.server; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +// prevents this module from applying when reactor-netty 0.8.0 is present +@SkipIfPresent(originalName = "reactor.netty.http.server.HttpTrafficHandler") +class HttpTrafficHandler_Skip { +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java new file mode 100644 index 0000000000..394d4e5651 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java @@ -0,0 +1,39 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitMany { + + private List result = null; + private CompletableFuture> f; + + public AwaitMany() { + f = new CompletableFuture<>(); + } + + public List await() { + List s = Collections.emptyList(); + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void onError(Throwable t) { + result.add(t.getMessage()); + f.completeExceptionally(t); + } + + public void done(List result) { + System.out.println("AwaitMany done"); + this.result = result; + f.complete(result); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java new file mode 100644 index 0000000000..44434be273 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java @@ -0,0 +1,32 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitSingle { + + private String result = null; + private CompletableFuture f; + + public AwaitSingle() { + f = new CompletableFuture(); + } + + public String getResult() { + return result; + } + + public String await() { + String s = null; + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void setResult(String s) { + result = s; + f.complete(result); + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java new file mode 100644 index 0000000000..ceddfe3b71 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor.test; + +import org.reactivestreams.Subscription; + +import com.newrelic.api.agent.Trace; + +import reactor.core.CoreSubscriber; + +public class MonoCoreSubscriber implements CoreSubscriber { + + @Override + @Trace + public void onNext(String t) { + System.out.println("Received string for onNext: " + t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Received error for onError: " + t); + } + + @Override + @Trace + public void onComplete() { + System.out.println("Mono has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription var1) { + System.out.println("Mono was subscribed to by : " + var1); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java new file mode 100644 index 0000000000..b3a2dce717 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java @@ -0,0 +1,23 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriptionConsumer implements Consumer { + + @Override + @Trace + public void accept(Subscription subscription) { + pause(); + + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java new file mode 100644 index 0000000000..2000a67242 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java @@ -0,0 +1,302 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.agent.introspec.*; +import com.newrelic.api.agent.Trace; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.*; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = "reactor.core") +public class TestApplication { + + private static final String txn1 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoSub"; + private static final String txn2 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoPub"; + private static final String txn3 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxSub"; + private static final String txn4 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxPub"; + private static final String WRAPPER = "Java/com.nr.instrumentation.reactor.NRRunnableWrapper/run"; + private static final String MONO_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onNext"; + private static final String MONO_COMPLETE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onComplete"; + private static final String MONO_SUBSCRIBE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onSubscribe"; + private static final String[] fluxArray = {"Message 1", "Message 2", "Message 3", "Message 4"}; + + @Test + public void doMonoSubscribeOnTest() { + testMonoSub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transactions", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn1); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn1); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn1); + System.out.println("Transaction traces: " + traces.size()); + for (TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the subscribing occurs on another thread + */ + for (TraceSegment child : children) { + if (child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if (initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if (attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + break; + } + } + Assert.assertTrue(passes); + + } + } + + @Test + public void doMonoPublishOnTest() { + testMonoPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn2); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn2); + System.out.println("Transaction traces: " + traces.size()); + for(TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the publishing occurs on another thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if(initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if(attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + } + } + Assert.assertTrue(passes); + + } + + } + + @Test + public void doFluxPublishOnTest() { + testFluxPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn4); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn4); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext")); + TracedMetricData metricData = metrics.get("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"); + Assert.assertNotNull(metricData); + Assert.assertEquals(4, metricData.getCallCount()); + + } + + @Test + public void doFluxSubscribeOnTest() { + testFluxSub(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn3); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn3); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + } + + @Test + public void doScheduleTest() { + testScheduler(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + String txnName = introspector.getTransactionNames().iterator().next(); + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Collection traces = introspector.getTransactionTracesForTransaction(txnName); + TransactionTrace transactionTrace = traces.iterator().next(); + Assert.assertNotNull(transactionTrace); + TraceSegment initial = transactionTrace.getInitialTraceSegment(); + Assert.assertNotNull(initial); + List children = initial.getChildren(); + boolean passes = false; + /* + Ensure that execution is dispatched to another thread and that Mono actions occur on that thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + List wrapperChildren = child.getChildren(); + Set childNames = new HashSet<>(); + for(TraceSegment wrapperChild : wrapperChildren) { + childNames.add(wrapperChild.getName()); + } + Assert.assertTrue(childNames.contains(MONO_SUBSCRIBE)); + Assert.assertTrue(childNames.contains(MONO_NEXT)); + Assert.assertTrue(childNames.contains(MONO_COMPLETE)); + } + } + } + + + @Trace(dispatcher = true) + public void testScheduler() { + System.out.println("Enter testScheduler"); + Mono mono = getStringMono(); + AwaitSingle await = new AwaitSingle(); + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + mono.subscribe(new TestMonoCoreSubscriber(await)); + }); + String result = await.await(); + System.out.println("TestScheduler result: " + result); + + } + + @Trace(dispatcher = true) + public void testMonoPub() { + System.out.println("Enter testMonoPub"); + AwaitSingle await = new AwaitSingle(); + Mono mono = getStringMono(); + + mono.publishOn(Schedulers.single()).subscribe(new TestMonoCoreSubscriber(await)); + await.await(); + + System.out.println("Exit testMonoPub with result: " + await.getResult()); + + } + + @Trace(dispatcher = true) + public void testMonoSub() { + System.out.println("Enter testMonoSub"); + AwaitSingle await = new AwaitSingle(); + + Mono mono = getStringMono().subscribeOn(Schedulers.single()).doOnSubscribe(new SubscriptionConsumer()); + + mono.subscribe(new TestMonoCoreSubscriber(await)); + String result = await.await(); + System.out.println("Exit testMonoSub with result: " + result); + + } + + @Trace(dispatcher = true) + public void testFluxSub() { + System.out.println("Enter testFluxSub"); + + Flux flux = getStringFlux().subscribeOn(Schedulers.single()); + + flux.subscribe(this::doSubscribeAction); + + List list = flux.collectList().block(); + System.out.println("Exit testFluxSub with result: " + list); + } + + @Trace(dispatcher = true) + public void testFluxPub() { + System.out.println("Enter testFluxPub"); + Flux flux = getStringFlux().publishOn(Schedulers.single()); + AwaitMany await = new AwaitMany(); + + flux.subscribe(new TestFluxCoreSubscriber(await,4)); + + List list = await.await(); + System.out.println("Exit testFluxPub with result: "+ list); + + } + + + + public Flux getStringFlux() { + return Flux.fromArray(fluxArray); + } + + public Mono getStringMono() { + + + return Mono.fromCallable(() -> { + try { + Thread.sleep(100L); + } catch(Exception ignored) { + + } + return "hello"; + }); + } + + @Trace + public void doSubscribeAction(String s) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Result is "+s); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java new file mode 100644 index 0000000000..0d87eb60d0 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java @@ -0,0 +1,52 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +import java.util.ArrayList; +import java.util.List; + +public class TestFluxCoreSubscriber implements CoreSubscriber { + + private AwaitMany await = null; + List result = null; + private int numberOfItems = 0; + public TestFluxCoreSubscriber(AwaitMany a, int numberOfItems) { + result = new ArrayList<>(); + await = a; + this.numberOfItems = numberOfItems; + } + + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result.add(t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: " + t.getMessage()); + if (await != null) { + List result = new ArrayList<>(); + result.add(t.getMessage()); + await.done(result); + } + + } + + @Override + @Trace + public void onComplete() { + await.done(result); + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(numberOfItems); + } + +} diff --git a/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java new file mode 100644 index 0000000000..0648030287 --- /dev/null +++ b/instrumentation/reactor-core-3.3.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java @@ -0,0 +1,50 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class TestMonoCoreSubscriber implements CoreSubscriber { + + private AwaitSingle await = null; + String result = null; + + public TestMonoCoreSubscriber(AwaitSingle a) { + await = a; + } + + @Override + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result = t; + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: "+t.getMessage()); + if(await != null) { + await.setResult(t.getMessage()); + } + + } + + @Override + @Trace + public void onComplete() { + if(await != null) { + synchronized(await) { + await.setResult(result); + } + } + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(1); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/build.gradle b/instrumentation/reactor-core-3.4.0/build.gradle new file mode 100644 index 0000000000..fce386c971 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("io.projectreactor:reactor-core:3.4.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-core-3.4.0' } +} + +verifyInstrumentation { + passesOnly 'io.projectreactor:reactor-core:[3.4.0,3.4.10)' +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java new file mode 100644 index 0000000000..6fbcb1db51 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnableWrapper implements Runnable { + + private Runnable delegate = null; + + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnableWrapper(Runnable r, Token t) { + delegate = r; + token = t; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + @Trace(async=true, excludeFromTransactionTrace = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java new file mode 100644 index 0000000000..d90133121f --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java @@ -0,0 +1,14 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; + +public class ReactorConfig { + + public static final boolean errorsEnabledNetty = NewRelic.getAgent().getConfig().getValue("reactor-netty.errors.enabled", false); + public static final boolean errorsRectorEnabled = NewRelic.getAgent().getConfig().getValue("reactor.errors.enabled", false); + public static final boolean errorsEnabled = errorsRectorEnabled || errorsEnabledNetty; + + private ReactorConfig() { + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java new file mode 100644 index 0000000000..8538825ebe --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java @@ -0,0 +1,25 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class ReactorUtils { + + public static NRRunnableWrapper getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnableWrapper) {return null;} + + Token currentToken = NewRelic.getAgent().getTransaction().getToken(); + if(currentToken != null) { + if(currentToken.isActive()) { + return new NRRunnableWrapper(r, currentToken); + } else { + currentToken.expire(); + currentToken = null; + return null; + } + } + + return null; + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java new file mode 100644 index 0000000000..b6c1d1f337 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.BaseSubscriber") +public class BaseSubscriber_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java new file mode 100644 index 0000000000..729236d716 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.DirectProcessor") +public class DirectProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java new file mode 100644 index 0000000000..127a9b224f --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java @@ -0,0 +1,34 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EmitterProcessor") +public class EmitterProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitError(Throwable t) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java new file mode 100644 index 0000000000..e47e9d4302 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java @@ -0,0 +1,195 @@ +package reactor.core.publisher; + +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.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.FluxCreate") +class FluxCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BaseSink", type = MatchType.BaseClass) + static abstract class BaseSink_Instrumentation { + + @NewField + protected Token token; + + BaseSink_Instrumentation(CoreSubscriber actual) { + token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + public void cancel() { + if (token != null) { + token.expire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if (token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BufferAsyncSink") + static final class BufferAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + BufferAsyncSink_Instrumentation(CoreSubscriber actual, int capacityHint) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$IgnoreSink") + static final class IgnoreSink_Instrumentation extends BaseSink_Instrumentation { + + IgnoreSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$NoOverflowBaseAsyncSink", type = MatchType.BaseClass) + static abstract class NoOverflowBaseAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + NoOverflowBaseAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializedFluxSink") + static final class FluxCreate$SerializedFluxSink_Instrumentation { + + @NewField + private Token token; + + FluxCreate$SerializedFluxSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializeOnRequestSink") + static class SerializeOnRequestSink_Instrumentation { + + @NewField + private Token token; + + SerializeOnRequestSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java new file mode 100644 index 0000000000..12620031a7 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java @@ -0,0 +1,74 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.MonoCreate") +class MonoCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.MonoCreate$DefaultMonoSink") + static final class DefaultMonoSink_Instrumentation { + + @NewField + private Token token; + + DefaultMonoSink_Instrumentation(CoreSubscriber actual) { + this.token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void cancel() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success(T value) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if(token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java new file mode 100644 index 0000000000..b5787fc50a --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java @@ -0,0 +1,34 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.NextProcessor") +class NextProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitError(Throwable cause) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(O value) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java new file mode 100644 index 0000000000..1b689b3146 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.ReplayProcessor") +public class ReplayProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java new file mode 100644 index 0000000000..6a08756ab5 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java @@ -0,0 +1,23 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptyMulticast") +class SinkEmptyMulticast_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java new file mode 100644 index 0000000000..346d6cc8da --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java @@ -0,0 +1,24 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +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.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptySerialized", type = MatchType.BaseClass) +class SinkEmptySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java new file mode 100644 index 0000000000..903c0f5643 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManyBestEffort") +class SinkManyBestEffort_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java new file mode 100644 index 0000000000..81d384b6f4 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManySerialized") +class SinkManySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java new file mode 100644 index 0000000000..57a8af8c97 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java @@ -0,0 +1,14 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "reactor.core.publisher.SinkOneSerialized") +public abstract class SinkOneSerialized_Instrumentation extends SinkEmptySerialized_Instrumentation{ + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java new file mode 100644 index 0000000000..4f10a4d35f --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java @@ -0,0 +1,28 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastManySinkNoBackpressure") +class UnicastManySinkNoBackpressure_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java new file mode 100644 index 0000000000..868972dd80 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java @@ -0,0 +1,36 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastProcessor") +public class UnicastProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java new file mode 100644 index 0000000000..ac922fbc8e --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java @@ -0,0 +1,7 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +@SkipIfPresent(originalName = "reactor.core.scheduler.ElasticScheduler$DirectScheduleTask") +public class DirectScheduleTask_Skip { +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java new file mode 100644 index 0000000000..7b548210b3 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java @@ -0,0 +1,51 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; +import reactor.core.Disposable; + +import java.util.concurrent.TimeUnit; + +@Weave(originalName = "reactor.core.scheduler.Scheduler", type = MatchType.Interface) +public class Scheduler_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + @Weave(originalName = "reactor.core.scheduler.Scheduler$Worker", type = MatchType.Interface) + public static class Worker_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java new file mode 100644 index 0000000000..7f19cfb2aa --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java @@ -0,0 +1,44 @@ +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; + +import reactor.core.Disposable; + +@Weave(originalName = "reactor.core.scheduler.Schedulers") +public class Schedulers_Instrumentation { + + @Trace + static Disposable directSchedule(ScheduledExecutorService exec, Runnable task, Disposable parent, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } + + public static Scheduler single() { + return Weaver.callOriginal(); + } + + @Trace + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java new file mode 100644 index 0000000000..4831e7c131 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java @@ -0,0 +1,15 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package reactor.netty.http.server; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +// prevents this module from applying when reactor-netty 0.8.0 is present +@SkipIfPresent(originalName = "reactor.netty.http.server.HttpTrafficHandler") +class HttpTrafficHandler_Skip { +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java new file mode 100644 index 0000000000..3bd1c303c8 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java @@ -0,0 +1,37 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitMany { + + private List result = null; + private CompletableFuture> f; + + public AwaitMany() { + f = new CompletableFuture<>(); + } + + public List await() { + List s = Collections.emptyList(); + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void onError(Throwable t) { + result.add(t.getMessage()); + f.completeExceptionally(t); + } + + public void done(List result) { + System.out.println("AwaitMany done"); + this.result = result; + f.complete(result); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java new file mode 100644 index 0000000000..44434be273 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java @@ -0,0 +1,32 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitSingle { + + private String result = null; + private CompletableFuture f; + + public AwaitSingle() { + f = new CompletableFuture(); + } + + public String getResult() { + return result; + } + + public String await() { + String s = null; + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void setResult(String s) { + result = s; + f.complete(result); + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java new file mode 100644 index 0000000000..acca8cd6f8 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java @@ -0,0 +1,33 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class MonoCoreSubscriber implements CoreSubscriber { + + @Override + @Trace + public void onNext(String t) { + System.out.println("Received string for onNext: " + t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Received error for onError: " + t); + } + + @Override + @Trace + public void onComplete() { + System.out.println("Mono has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription var1) { + System.out.println("Mono was subscribed to by : " + var1); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java new file mode 100644 index 0000000000..b3a2dce717 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java @@ -0,0 +1,23 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriptionConsumer implements Consumer { + + @Override + @Trace + public void accept(Subscription subscription) { + pause(); + + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java new file mode 100644 index 0000000000..0f936b5dcd --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java @@ -0,0 +1,413 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.agent.introspec.*; +import com.newrelic.api.agent.Trace; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.*; +import java.util.concurrent.*; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = "reactor.core") +public class TestApplication { + + private static final String TRY_EMIT = "Java/reactor.core.publisher.NextProcessor/tryEmitValue"; + private static final String MONO_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onNext"; + private static final String MONO_COMPLETE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onComplete"; + private static final String MONO_SUBSCRIBE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onSubscribe"; + private static final String BEST_EFFORT_NEXT = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitNext"; + private static final String SERIALIZED_NEXT = "Java/reactor.core.publisher.SinkManySerialized/tryEmitNext"; + private static final String SUBSCRIBER_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"; + private static final String SERIALIZED_COMPLETE = "Java/reactor.core.publisher.SinkManySerialized/tryEmitComplete"; + private static final String BEST_EFFORT_COMPLETE = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitComplete"; + private static final String txn1 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoSub"; + private static final String txn2 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoPub"; + private static final String txn3 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxSub"; + private static final String txn4 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxPub"; + private static final String WRAPPER = "Java/com.nr.instrumentation.reactor.NRRunnableWrapper/run"; + private static final String[] fluxArray = {"Message 1", "Message 2", "Message 3", "Message 4"}; + + @Test + public void doSinkOneTest() { + testSinkOne(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(TRY_EMIT)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + } + + @Trace(dispatcher = true) + public void testSinkOne() { + + Sinks.One sink = Sinks.one(); + Mono mono = sink.asMono(); + + AwaitSingle awaitSingle = new AwaitSingle(); + mono.subscribe(new TestMonoCoreSubscriber(awaitSingle)); + + Schedulers.single().schedule(() -> { + Sinks.EmitResult result = sink.tryEmitValue("Hello"); + if(result.isFailure()) { + System.out.println("EmitResult threw an error: "+ result); + } + }); + + String result = awaitSingle.await(); + System.out.println("Await Single result : " + result); + } + + @Test + public void doTestSinkMany() { + testSinkMany(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(BEST_EFFORT_NEXT)); + Assert.assertTrue(names.contains(SERIALIZED_NEXT)); + Assert.assertTrue(names.contains(SUBSCRIBER_NEXT)); + Assert.assertTrue(names.contains(BEST_EFFORT_COMPLETE)); + Assert.assertTrue(names.contains(SERIALIZED_COMPLETE)); + + TracedMetricData metric = metrics.get(BEST_EFFORT_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SUBSCRIBER_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(BEST_EFFORT_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + } + + @Trace(dispatcher = true) + public void testSinkMany() { + Sinks.Many hotSource = Sinks.many().multicast().directBestEffort(); + + Flux flux = hotSource.asFlux(); + AwaitMany awaitMany = new AwaitMany(); + flux.subscribe(new TestFluxCoreSubscriber(awaitMany, 5)); + + Scheduler scheduler = Schedulers.single(); + String[] colors = new String[]{"Red", "Green", "Blue", "Pink", "Purple"}; + for(String color : colors) { + CompletableFuture completableFuture = new CompletableFuture<>(); + scheduler.schedule(() -> { + hotSource.emitNext(color, Sinks.EmitFailureHandler.FAIL_FAST); + completableFuture.complete(true); + }, 100, TimeUnit.MILLISECONDS); + + try { + Boolean result = completableFuture.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + + } + hotSource.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + List results = awaitMany.await(); + System.out.println("Await Many result : " + results); + } + + @Test + public void doMonoSubscribeOnTest() { + testMonoSub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transactions", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn1); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn1); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn1); + System.out.println("Transaction traces: " + traces.size()); + for (TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the subscribing occurs on another thread + */ + for (TraceSegment child : children) { + if (child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if (initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if (attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + break; + } + } + Assert.assertTrue(passes); + + } + } + + @Test + public void doMonoPublishOnTest() { + testMonoPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn2); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn2); + System.out.println("Transaction traces: " + traces.size()); + for(TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the publishing occurs on another thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if(initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if(attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + } + } + Assert.assertTrue(passes); + + } + + } + + @Test + public void doFluxPublishOnTest() { + testFluxPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn4); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn4); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext")); + TracedMetricData metricData = metrics.get("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"); + Assert.assertNotNull(metricData); + Assert.assertEquals(4, metricData.getCallCount()); + + } + + @Test + public void doFluxSubscribeOnTest() { + testFluxSub(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn3); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn3); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + } + + @Test + public void doScheduleTest() { + testScheduler(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + String txnName = introspector.getTransactionNames().iterator().next(); + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Collection traces = introspector.getTransactionTracesForTransaction(txnName); + TransactionTrace transactionTrace = traces.iterator().next(); + Assert.assertNotNull(transactionTrace); + TraceSegment initial = transactionTrace.getInitialTraceSegment(); + Assert.assertNotNull(initial); + List children = initial.getChildren(); + boolean passes = false; + /* + Ensure that execution is dispatched to another thread and that Mono actions occur on that thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + List wrapperChildren = child.getChildren(); + Set childNames = new HashSet<>(); + for(TraceSegment wrapperChild : wrapperChildren) { + childNames.add(wrapperChild.getName()); + } + Assert.assertTrue(childNames.contains(MONO_SUBSCRIBE)); + Assert.assertTrue(childNames.contains(MONO_NEXT)); + Assert.assertTrue(childNames.contains(MONO_COMPLETE)); + } + } + } + + + @Trace(dispatcher = true) + public void testScheduler() { + System.out.println("Enter testScheduler"); + Mono mono = getStringMono(); + AwaitSingle await = new AwaitSingle(); + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + mono.subscribe(new TestMonoCoreSubscriber(await)); + }); + String result = await.await(); + System.out.println("TestScheduler result: " + result); + + } + + @Trace(dispatcher = true) + public void testMonoPub() { + System.out.println("Enter testMonoPub"); + AwaitSingle await = new AwaitSingle(); + Mono mono = getStringMono(); + + mono.publishOn(Schedulers.single()).subscribe(new TestMonoCoreSubscriber(await)); + await.await(); + + System.out.println("Exit testMonoPub with result: " + await.getResult()); + + } + + @Trace(dispatcher = true) + public void testMonoSub() { + System.out.println("Enter testMonoSub"); + AwaitSingle await = new AwaitSingle(); + + Mono mono = getStringMono().subscribeOn(Schedulers.single()).doOnSubscribe(new SubscriptionConsumer()); + + mono.subscribe(new TestMonoCoreSubscriber(await)); + String result = await.await(); + System.out.println("Exit testMonoSub with result: " + result); + + } + + @Trace(dispatcher = true) + public void testFluxSub() { + System.out.println("Enter testFluxSub"); + + Flux flux = getStringFlux().subscribeOn(Schedulers.single()); + + flux.subscribe(this::doSubscribeAction); + + List list = flux.collectList().block(); + System.out.println("Exit testFluxSub with result: " + list); + } + + @Trace(dispatcher = true) + public void testFluxPub() { + System.out.println("Enter testFluxPub"); + Flux flux = getStringFlux().publishOn(Schedulers.single()); + AwaitMany await = new AwaitMany(); + + flux.subscribe(new TestFluxCoreSubscriber(await,4)); + + List list = await.await(); + System.out.println("Exit testFluxPub with result: "+ list); + + } + + + public Flux getStringFlux() { + return Flux.fromArray(fluxArray); + } + + public Mono getStringMono() { + + + return Mono.fromCallable(() -> { + try { + Thread.sleep(100L); + } catch(Exception ignored) { + + } + return "hello"; + }); + } + + @Trace + public void doSubscribeAction(String s) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Result is "+s); + } + + +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java new file mode 100644 index 0000000000..0d87eb60d0 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java @@ -0,0 +1,52 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +import java.util.ArrayList; +import java.util.List; + +public class TestFluxCoreSubscriber implements CoreSubscriber { + + private AwaitMany await = null; + List result = null; + private int numberOfItems = 0; + public TestFluxCoreSubscriber(AwaitMany a, int numberOfItems) { + result = new ArrayList<>(); + await = a; + this.numberOfItems = numberOfItems; + } + + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result.add(t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: " + t.getMessage()); + if (await != null) { + List result = new ArrayList<>(); + result.add(t.getMessage()); + await.done(result); + } + + } + + @Override + @Trace + public void onComplete() { + await.done(result); + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(numberOfItems); + } + +} diff --git a/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java new file mode 100644 index 0000000000..0648030287 --- /dev/null +++ b/instrumentation/reactor-core-3.4.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java @@ -0,0 +1,50 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class TestMonoCoreSubscriber implements CoreSubscriber { + + private AwaitSingle await = null; + String result = null; + + public TestMonoCoreSubscriber(AwaitSingle a) { + await = a; + } + + @Override + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result = t; + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: "+t.getMessage()); + if(await != null) { + await.setResult(t.getMessage()); + } + + } + + @Override + @Trace + public void onComplete() { + if(await != null) { + synchronized(await) { + await.setResult(result); + } + } + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(1); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/build.gradle b/instrumentation/reactor-core-3.4.10/build.gradle new file mode 100644 index 0000000000..6536999932 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("io.projectreactor:reactor-core:3.4.10") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-core-3.4.10' } +} + +verifyInstrumentation { + passesOnly 'io.projectreactor:reactor-core:[3.4.10,3.5.0)' +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java new file mode 100644 index 0000000000..d852bd1868 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnableWrapper implements Runnable { + + private Runnable delegate = null; + + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnableWrapper(Runnable r, Token t) { + delegate = r; + token = t; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + @Trace(async = true, excludeFromTransactionTrace = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java new file mode 100644 index 0000000000..d90133121f --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java @@ -0,0 +1,14 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; + +public class ReactorConfig { + + public static final boolean errorsEnabledNetty = NewRelic.getAgent().getConfig().getValue("reactor-netty.errors.enabled", false); + public static final boolean errorsRectorEnabled = NewRelic.getAgent().getConfig().getValue("reactor.errors.enabled", false); + public static final boolean errorsEnabled = errorsRectorEnabled || errorsEnabledNetty; + + private ReactorConfig() { + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java new file mode 100644 index 0000000000..8538825ebe --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java @@ -0,0 +1,25 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class ReactorUtils { + + public static NRRunnableWrapper getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnableWrapper) {return null;} + + Token currentToken = NewRelic.getAgent().getTransaction().getToken(); + if(currentToken != null) { + if(currentToken.isActive()) { + return new NRRunnableWrapper(r, currentToken); + } else { + currentToken.expire(); + currentToken = null; + return null; + } + } + + return null; + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java new file mode 100644 index 0000000000..b6c1d1f337 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.BaseSubscriber") +public class BaseSubscriber_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java new file mode 100644 index 0000000000..729236d716 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.DirectProcessor") +public class DirectProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java new file mode 100644 index 0000000000..61dcabfdba --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java @@ -0,0 +1,36 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EmitterProcessor") +public class EmitterProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java new file mode 100644 index 0000000000..054e9a7e30 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java @@ -0,0 +1,195 @@ +package reactor.core.publisher; + +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.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.FluxCreate") +class FluxCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BaseSink", type = MatchType.BaseClass) + static abstract class BaseSink_Instrumentation { + + @NewField + protected Token token; + + BaseSink_Instrumentation(CoreSubscriber actual) { + token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + public void cancel() { + if (token != null) { + token.expire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if (token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BufferAsyncSink") + static final class BufferAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + BufferAsyncSink_Instrumentation(CoreSubscriber actual, int capacityHint) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$IgnoreSink") + static final class IgnoreSink_Instrumentation extends BaseSink_Instrumentation { + + IgnoreSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$NoOverflowBaseAsyncSink", type = MatchType.BaseClass) + static abstract class NoOverflowBaseAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + NoOverflowBaseAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializedFluxSink") + static final class FluxCreate$SerializedFluxSink_Instrumentation { + + @NewField + private Token token; + + FluxCreate$SerializedFluxSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializeOnRequestSink") + static class SerializeOnRequestSink_Instrumentation { + + @NewField + private Token token; + + SerializeOnRequestSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java new file mode 100644 index 0000000000..12620031a7 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java @@ -0,0 +1,74 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.MonoCreate") +class MonoCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.MonoCreate$DefaultMonoSink") + static final class DefaultMonoSink_Instrumentation { + + @NewField + private Token token; + + DefaultMonoSink_Instrumentation(CoreSubscriber actual) { + this.token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void cancel() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success(T value) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if(token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java new file mode 100644 index 0000000000..4bbfe04433 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java @@ -0,0 +1,32 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.NextProcessor") +class NextProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + Sinks.EmitResult tryEmitError(Throwable cause) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + Sinks.EmitResult tryEmitValue(O value) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java new file mode 100644 index 0000000000..09aaae8fe4 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java @@ -0,0 +1,28 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.ReplayProcessor") +public class ReplayProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java new file mode 100644 index 0000000000..6a08756ab5 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java @@ -0,0 +1,23 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptyMulticast") +class SinkEmptyMulticast_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java new file mode 100644 index 0000000000..346d6cc8da --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java @@ -0,0 +1,24 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +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.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptySerialized", type = MatchType.BaseClass) +class SinkEmptySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java new file mode 100644 index 0000000000..3a4cc37ccd --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManyBestEffort") +class SinkManyBestEffort_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java new file mode 100644 index 0000000000..81d384b6f4 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManySerialized") +class SinkManySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java new file mode 100644 index 0000000000..05096d4c55 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java @@ -0,0 +1,15 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "reactor.core.publisher.SinkOneMulticast") +class SinkOneMulticast_Instrumentation extends SinkEmptyMulticast_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(O value) { + return Weaver.callOriginal(); + + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java new file mode 100644 index 0000000000..57a8af8c97 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java @@ -0,0 +1,14 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "reactor.core.publisher.SinkOneSerialized") +public abstract class SinkOneSerialized_Instrumentation extends SinkEmptySerialized_Instrumentation{ + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java new file mode 100644 index 0000000000..4f10a4d35f --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Instrumentation.java @@ -0,0 +1,28 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastManySinkNoBackpressure") +class UnicastManySinkNoBackpressure_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java new file mode 100644 index 0000000000..868972dd80 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java @@ -0,0 +1,36 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastProcessor") +public class UnicastProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java new file mode 100644 index 0000000000..ac922fbc8e --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java @@ -0,0 +1,7 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +@SkipIfPresent(originalName = "reactor.core.scheduler.ElasticScheduler$DirectScheduleTask") +public class DirectScheduleTask_Skip { +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java new file mode 100644 index 0000000000..7b548210b3 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java @@ -0,0 +1,51 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; +import reactor.core.Disposable; + +import java.util.concurrent.TimeUnit; + +@Weave(originalName = "reactor.core.scheduler.Scheduler", type = MatchType.Interface) +public class Scheduler_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + @Weave(originalName = "reactor.core.scheduler.Scheduler$Worker", type = MatchType.Interface) + public static class Worker_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java new file mode 100644 index 0000000000..2a4b38ae54 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java @@ -0,0 +1,45 @@ +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; + +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +@Weave(originalName = "reactor.core.scheduler.Schedulers") +public class Schedulers_Instrumentation { + + @Trace + static Disposable directSchedule(ScheduledExecutorService exec, Runnable task, Disposable parent, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } + + public static Scheduler single() { + return Weaver.callOriginal(); + } + + @Trace + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java new file mode 100644 index 0000000000..4831e7c131 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java @@ -0,0 +1,15 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package reactor.netty.http.server; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +// prevents this module from applying when reactor-netty 0.8.0 is present +@SkipIfPresent(originalName = "reactor.netty.http.server.HttpTrafficHandler") +class HttpTrafficHandler_Skip { +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java new file mode 100644 index 0000000000..3bd1c303c8 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java @@ -0,0 +1,37 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitMany { + + private List result = null; + private CompletableFuture> f; + + public AwaitMany() { + f = new CompletableFuture<>(); + } + + public List await() { + List s = Collections.emptyList(); + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void onError(Throwable t) { + result.add(t.getMessage()); + f.completeExceptionally(t); + } + + public void done(List result) { + System.out.println("AwaitMany done"); + this.result = result; + f.complete(result); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java new file mode 100644 index 0000000000..44434be273 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java @@ -0,0 +1,32 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitSingle { + + private String result = null; + private CompletableFuture f; + + public AwaitSingle() { + f = new CompletableFuture(); + } + + public String getResult() { + return result; + } + + public String await() { + String s = null; + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void setResult(String s) { + result = s; + f.complete(result); + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java new file mode 100644 index 0000000000..acca8cd6f8 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java @@ -0,0 +1,33 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class MonoCoreSubscriber implements CoreSubscriber { + + @Override + @Trace + public void onNext(String t) { + System.out.println("Received string for onNext: " + t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Received error for onError: " + t); + } + + @Override + @Trace + public void onComplete() { + System.out.println("Mono has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription var1) { + System.out.println("Mono was subscribed to by : " + var1); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java new file mode 100644 index 0000000000..b3a2dce717 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java @@ -0,0 +1,23 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriptionConsumer implements Consumer { + + @Override + @Trace + public void accept(Subscription subscription) { + pause(); + + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java new file mode 100644 index 0000000000..4f78dbd834 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java @@ -0,0 +1,414 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.agent.introspec.*; +import com.newrelic.api.agent.Trace; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.*; +import java.util.concurrent.*; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = "reactor.core") +public class TestApplication { + + + private static final String TRY_EMIT = "Java/reactor.core.publisher.SinkOneSerialized/tryEmitValue"; + private static final String TRY_EMIT2 = "Java/reactor.core.publisher.SinkOneMulticast/tryEmitValue"; + private static final String MONO_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onNext"; + private static final String MONO_COMPLETE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onComplete"; + private static final String MONO_SUBSCRIBE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onSubscribe"; + private static final String BEST_EFFORT_NEXT = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitNext"; + private static final String SERIALIZED_NEXT = "Java/reactor.core.publisher.SinkManySerialized/tryEmitNext"; + private static final String SUBSCRIBER_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"; + private static final String SERIALIZED_COMPLETE = "Java/reactor.core.publisher.SinkManySerialized/tryEmitComplete"; + private static final String BEST_EFFORT_COMPLETE = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitComplete"; + private static final String txn1 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoSub"; + private static final String txn2 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoPub"; + private static final String txn3 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxSub"; + private static final String txn4 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxPub"; + private static final String WRAPPER = "Java/com.nr.instrumentation.reactor.NRRunnableWrapper/run"; + private static final String[] fluxArray = {"Message 1", "Message 2", "Message 3", "Message 4"}; + + @Test + public void doSinkOneTest() { + testSinkOne(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(TRY_EMIT)); + Assert.assertTrue(names.contains(TRY_EMIT2)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + } + + @Trace(dispatcher = true) + public void testSinkOne() { + + Sinks.One sink = Sinks.one(); + Mono mono = sink.asMono(); + + AwaitSingle awaitSingle = new AwaitSingle(); + mono.subscribe(new TestMonoCoreSubscriber(awaitSingle)); + + Schedulers.single().schedule(() -> { + Sinks.EmitResult result = sink.tryEmitValue("Hello"); + if(result.isFailure()) { + System.out.println("EmitResult threw an error: "+ result); + } + }); + + String result = awaitSingle.await(); + System.out.println("Await Single result : " + result); + } + + @Test + public void doTestSinkMany() { + testSinkMany(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(BEST_EFFORT_NEXT)); + Assert.assertTrue(names.contains(SERIALIZED_NEXT)); + Assert.assertTrue(names.contains(SUBSCRIBER_NEXT)); + Assert.assertTrue(names.contains(BEST_EFFORT_COMPLETE)); + Assert.assertTrue(names.contains(SERIALIZED_COMPLETE)); + + TracedMetricData metric = metrics.get(BEST_EFFORT_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SUBSCRIBER_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(BEST_EFFORT_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + } + + @Trace(dispatcher = true) + public void testSinkMany() { + Sinks.Many hotSource = Sinks.many().multicast().directBestEffort(); + + Flux flux = hotSource.asFlux(); + AwaitMany awaitMany = new AwaitMany(); + flux.subscribe(new TestFluxCoreSubscriber(awaitMany, 5)); + + Scheduler scheduler = Schedulers.single(); + String[] colors = new String[]{"Red", "Green", "Blue", "Pink", "Purple"}; + for(String color : colors) { + CompletableFuture completableFuture = new CompletableFuture<>(); + scheduler.schedule(() -> { + hotSource.emitNext(color, Sinks.EmitFailureHandler.FAIL_FAST); + completableFuture.complete(true); + }, 100, TimeUnit.MILLISECONDS); + + try { + Boolean result = completableFuture.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + + } + hotSource.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + List results = awaitMany.await(); + System.out.println("Await Many result : " + results); + } + + @Test + public void doMonoSubscribeOnTest() { + testMonoSub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transactions", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn1); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn1); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn1); + for (TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the subscribing occurs on another thread + */ + for (TraceSegment child : children) { + if (child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if (initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if (attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + break; + } + } + Assert.assertTrue(passes); + + } + } + + @Test + public void doMonoPublishOnTest() { + testMonoPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn2); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn2); + for(TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the publishing occurs on another thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if(initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if(attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + } + } + Assert.assertTrue(passes); + + } + + } + + @Test + public void doFluxPublishOnTest() { + testFluxPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn4); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn4); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext")); + TracedMetricData metricData = metrics.get("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"); + Assert.assertNotNull(metricData); + Assert.assertEquals(4, metricData.getCallCount()); + + } + + @Test + public void doFluxSubscribeOnTest() { + testFluxSub(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn3); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn3); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + } + + @Test + public void doScheduleTest() { + testScheduler(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + String txnName = introspector.getTransactionNames().iterator().next(); + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Collection traces = introspector.getTransactionTracesForTransaction(txnName); + TransactionTrace transactionTrace = traces.iterator().next(); + Assert.assertNotNull(transactionTrace); + TraceSegment initial = transactionTrace.getInitialTraceSegment(); + Assert.assertNotNull(initial); + List children = initial.getChildren(); + boolean passes = false; + /* + Ensure that execution is dispatched to another thread and that Mono actions occur on that thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + List wrapperChildren = child.getChildren(); + Set childNames = new HashSet<>(); + for(TraceSegment wrapperChild : wrapperChildren) { + childNames.add(wrapperChild.getName()); + } + Assert.assertTrue(childNames.contains(MONO_SUBSCRIBE)); + Assert.assertTrue(childNames.contains(MONO_NEXT)); + Assert.assertTrue(childNames.contains(MONO_COMPLETE)); + } + } + } + + + @Trace(dispatcher = true) + public void testScheduler() { + System.out.println("Enter testScheduler"); + Mono mono = getStringMono(); + AwaitSingle await = new AwaitSingle(); + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + mono.subscribe(new TestMonoCoreSubscriber(await)); + }); + String result = await.await(); + System.out.println("TestScheduler result: " + result); + + } + + @Trace(dispatcher = true) + public void testMonoPub() { + System.out.println("Enter testMonoPub"); + AwaitSingle await = new AwaitSingle(); + Mono mono = getStringMono(); + + mono.publishOn(Schedulers.single()).subscribe(new TestMonoCoreSubscriber(await)); + await.await(); + + System.out.println("Exit testMonoPub with result: " + await.getResult()); + + } + + @Trace(dispatcher = true) + public void testMonoSub() { + System.out.println("Enter testMonoSub"); + AwaitSingle await = new AwaitSingle(); + + Mono mono = getStringMono().subscribeOn(Schedulers.single()).doOnSubscribe(new SubscriptionConsumer()); + + mono.subscribe(new TestMonoCoreSubscriber(await)); + String result = await.await(); + System.out.println("Exit testMonoSub with result: " + result); + + } + + @Trace(dispatcher = true) + public void testFluxSub() { + System.out.println("Enter testFluxSub"); + + Flux flux = getStringFlux().subscribeOn(Schedulers.single()); + + flux.subscribe(this::doSubscribeAction); + + List list = flux.collectList().block(); + System.out.println("Exit testFluxSub with result: " + list); + } + + @Trace(dispatcher = true) + public void testFluxPub() { + System.out.println("Enter testFluxPub"); + Flux flux = getStringFlux().publishOn(Schedulers.single()); + AwaitMany await = new AwaitMany(); + + flux.subscribe(new TestFluxCoreSubscriber(await,4)); + + List list = await.await(); + System.out.println("Exit testFluxPub with result: "+ list); + + } + + + public Flux getStringFlux() { + return Flux.fromArray(fluxArray); + } + + public Mono getStringMono() { + + + return Mono.fromCallable(() -> { + try { + Thread.sleep(100L); + } catch(Exception ignored) { + + } + return "hello"; + }); + } + + @Trace + public void doSubscribeAction(String s) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Result is "+s); + } + + +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java new file mode 100644 index 0000000000..0d87eb60d0 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java @@ -0,0 +1,52 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +import java.util.ArrayList; +import java.util.List; + +public class TestFluxCoreSubscriber implements CoreSubscriber { + + private AwaitMany await = null; + List result = null; + private int numberOfItems = 0; + public TestFluxCoreSubscriber(AwaitMany a, int numberOfItems) { + result = new ArrayList<>(); + await = a; + this.numberOfItems = numberOfItems; + } + + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result.add(t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: " + t.getMessage()); + if (await != null) { + List result = new ArrayList<>(); + result.add(t.getMessage()); + await.done(result); + } + + } + + @Override + @Trace + public void onComplete() { + await.done(result); + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(numberOfItems); + } + +} diff --git a/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java new file mode 100644 index 0000000000..0648030287 --- /dev/null +++ b/instrumentation/reactor-core-3.4.10/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java @@ -0,0 +1,50 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class TestMonoCoreSubscriber implements CoreSubscriber { + + private AwaitSingle await = null; + String result = null; + + public TestMonoCoreSubscriber(AwaitSingle a) { + await = a; + } + + @Override + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result = t; + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: "+t.getMessage()); + if(await != null) { + await.setResult(t.getMessage()); + } + + } + + @Override + @Trace + public void onComplete() { + if(await != null) { + synchronized(await) { + await.setResult(result); + } + } + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(1); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/build.gradle b/instrumentation/reactor-core-3.5.0/build.gradle new file mode 100644 index 0000000000..251a59bef9 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/build.gradle @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":agent-bridge")) + implementation("io.projectreactor:reactor-core:3.5.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.reactor-core-3.5.0' } +} + +verifyInstrumentation { + passesOnly 'io.projectreactor:reactor-core:[3.5.0,)' +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java new file mode 100644 index 0000000000..d852bd1868 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/NRRunnableWrapper.java @@ -0,0 +1,35 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnableWrapper implements Runnable { + + private Runnable delegate = null; + + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnableWrapper(Runnable r, Token t) { + delegate = r; + token = t; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + @Trace(async = true, excludeFromTransactionTrace = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java new file mode 100644 index 0000000000..d90133121f --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorConfig.java @@ -0,0 +1,14 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; + +public class ReactorConfig { + + public static final boolean errorsEnabledNetty = NewRelic.getAgent().getConfig().getValue("reactor-netty.errors.enabled", false); + public static final boolean errorsRectorEnabled = NewRelic.getAgent().getConfig().getValue("reactor.errors.enabled", false); + public static final boolean errorsEnabled = errorsRectorEnabled || errorsEnabledNetty; + + private ReactorConfig() { + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java new file mode 100644 index 0000000000..8538825ebe --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/com/nr/instrumentation/reactor/ReactorUtils.java @@ -0,0 +1,25 @@ +package com.nr.instrumentation.reactor; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class ReactorUtils { + + public static NRRunnableWrapper getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnableWrapper) {return null;} + + Token currentToken = NewRelic.getAgent().getTransaction().getToken(); + if(currentToken != null) { + if(currentToken.isActive()) { + return new NRRunnableWrapper(r, currentToken); + } else { + currentToken.expire(); + currentToken = null; + return null; + } + } + + return null; + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java new file mode 100644 index 0000000000..b6c1d1f337 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/BaseSubscriber_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.BaseSubscriber") +public class BaseSubscriber_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java new file mode 100644 index 0000000000..729236d716 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/DirectProcessor_Instrumentation.java @@ -0,0 +1,17 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.DirectProcessor") +public class DirectProcessor_Instrumentation { + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java new file mode 100644 index 0000000000..127a9b224f --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/EmitterProcessor_Instrumentation.java @@ -0,0 +1,34 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.EmitterProcessor") +public class EmitterProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitError(Throwable t) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java new file mode 100644 index 0000000000..e47e9d4302 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/FluxCreate_Instrumentation.java @@ -0,0 +1,195 @@ +package reactor.core.publisher; + +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.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.FluxCreate") +class FluxCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BaseSink", type = MatchType.BaseClass) + static abstract class BaseSink_Instrumentation { + + @NewField + protected Token token; + + BaseSink_Instrumentation(CoreSubscriber actual) { + token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + public void cancel() { + if (token != null) { + token.expire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if (token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$BufferAsyncSink") + static final class BufferAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + BufferAsyncSink_Instrumentation(CoreSubscriber actual, int capacityHint) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$IgnoreSink") + static final class IgnoreSink_Instrumentation extends BaseSink_Instrumentation { + + IgnoreSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$NoOverflowBaseAsyncSink", type = MatchType.BaseClass) + static abstract class NoOverflowBaseAsyncSink_Instrumentation extends BaseSink_Instrumentation { + + NoOverflowBaseAsyncSink_Instrumentation(CoreSubscriber actual) { + super(actual); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializedFluxSink") + static final class FluxCreate$SerializedFluxSink_Instrumentation { + + @NewField + private Token token; + + FluxCreate$SerializedFluxSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } + + @Weave(originalName = "reactor.core.publisher.FluxCreate$SerializeOnRequestSink") + static class SerializeOnRequestSink_Instrumentation { + + @NewField + private Token token; + + SerializeOnRequestSink_Instrumentation(BaseSink_Instrumentation sink) { + if(sink != null) { + if(sink.token != null) { + token = sink.token; + } + } + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void complete() { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable t) { + if (token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public FluxSink next(T t) { + if (token != null) { + token.link(); + } + return Weaver.callOriginal(); + } + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java new file mode 100644 index 0000000000..12620031a7 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/MonoCreate_Instrumentation.java @@ -0,0 +1,74 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; +import reactor.core.CoreSubscriber; + +@Weave(originalName = "reactor.core.publisher.MonoCreate") +class MonoCreate_Instrumentation { + + @Weave(originalName = "reactor.core.publisher.MonoCreate$DefaultMonoSink") + static final class DefaultMonoSink_Instrumentation { + + @NewField + private Token token; + + DefaultMonoSink_Instrumentation(CoreSubscriber actual) { + this.token = NewRelic.getAgent().getTransaction().getToken(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void cancel() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void error(Throwable e) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(e); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void success(T value) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + Weaver.callOriginal(); + } + + @Trace(async = true, excludeFromTransactionTrace = true) + public void request(long n) { + if(token != null) { + token.link(); + } + Weaver.callOriginal(); + } + + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java new file mode 100644 index 0000000000..4a75132292 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/NextProcessor_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.NextProcessor") +class NextProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + Sinks.EmitResult tryEmitError(Throwable cause) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + Sinks.EmitResult tryEmitValue(O value) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java new file mode 100644 index 0000000000..09aaae8fe4 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/ReplayProcessor_Instrumentation.java @@ -0,0 +1,28 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.ReplayProcessor") +public class ReplayProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java new file mode 100644 index 0000000000..6a08756ab5 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptyMulticast_Instrumentation.java @@ -0,0 +1,23 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptyMulticast") +class SinkEmptyMulticast_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java new file mode 100644 index 0000000000..346d6cc8da --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkEmptySerialized_Instrumentation.java @@ -0,0 +1,24 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +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.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkEmptySerialized", type = MatchType.BaseClass) +class SinkEmptySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitEmpty() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java new file mode 100644 index 0000000000..3a4cc37ccd --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManyBestEffort_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManyBestEffort") +class SinkManyBestEffort_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java new file mode 100644 index 0000000000..81d384b6f4 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkManySerialized_Instrumentation.java @@ -0,0 +1,29 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.SinkManySerialized") +class SinkManySerialized_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + public Sinks.EmitResult tryEmitError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java new file mode 100644 index 0000000000..05096d4c55 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneMulticast_Instrumentation.java @@ -0,0 +1,15 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "reactor.core.publisher.SinkOneMulticast") +class SinkOneMulticast_Instrumentation extends SinkEmptyMulticast_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(O value) { + return Weaver.callOriginal(); + + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java new file mode 100644 index 0000000000..57a8af8c97 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/SinkOneSerialized_Instrumentation.java @@ -0,0 +1,14 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "reactor.core.publisher.SinkOneSerialized") +public abstract class SinkOneSerialized_Instrumentation extends SinkEmptySerialized_Instrumentation{ + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitValue(T t) { + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Skip.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Skip.java new file mode 100644 index 0000000000..efbf09c447 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastManySinkNoBackpressure_Skip.java @@ -0,0 +1,8 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +@SkipIfPresent(originalName = "reactor.core.publisher.UnicastManySinkNoBackpressure") +class UnicastManySinkNoBackpressure_Skip { + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java new file mode 100644 index 0000000000..5ff3003b34 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/publisher/UnicastProcessor_Instrumentation.java @@ -0,0 +1,34 @@ +package reactor.core.publisher; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.ReactorConfig; + +@Weave(originalName = "reactor.core.publisher.UnicastProcessor") +public class UnicastProcessor_Instrumentation { + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitComplete() { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitError(Throwable t) { + return Weaver.callOriginal(); + } + + @Trace(excludeFromTransactionTrace = true) + public Sinks.EmitResult tryEmitNext(T t) { + return Weaver.callOriginal(); + } + + public void onError(Throwable t) { + if(ReactorConfig.errorsEnabled) { + NewRelic.noticeError(t); + } + Weaver.callOriginal(); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java new file mode 100644 index 0000000000..ac922fbc8e --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/DirectScheduleTask_Skip.java @@ -0,0 +1,7 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +@SkipIfPresent(originalName = "reactor.core.scheduler.ElasticScheduler$DirectScheduleTask") +public class DirectScheduleTask_Skip { +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java new file mode 100644 index 0000000000..7b548210b3 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Scheduler_Instrumentation.java @@ -0,0 +1,51 @@ +package reactor.core.scheduler; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; +import reactor.core.Disposable; + +import java.util.concurrent.TimeUnit; + +@Weave(originalName = "reactor.core.scheduler.Scheduler", type = MatchType.Interface) +public class Scheduler_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + @Weave(originalName = "reactor.core.scheduler.Scheduler$Worker", type = MatchType.Interface) + public static class Worker_Instrumentation { + + public Disposable schedule(Runnable task) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if (wrapper != null) { + task = wrapper; + } + return Weaver.callOriginal(); + } + + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java new file mode 100644 index 0000000000..2a4b38ae54 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/core/scheduler/Schedulers_Instrumentation.java @@ -0,0 +1,45 @@ +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.instrumentation.reactor.NRRunnableWrapper; +import com.nr.instrumentation.reactor.ReactorUtils; + +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +@Weave(originalName = "reactor.core.scheduler.Schedulers") +public class Schedulers_Instrumentation { + + @Trace + static Disposable directSchedule(ScheduledExecutorService exec, Runnable task, Disposable parent, long delay, TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } + + public static Scheduler single() { + return Weaver.callOriginal(); + } + + @Trace + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + NRRunnableWrapper wrapper = ReactorUtils.getRunnableWrapper(task); + if(wrapper != null) { + task = wrapper; + } + + return Weaver.callOriginal(); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java new file mode 100644 index 0000000000..4831e7c131 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/main/java/reactor/netty/http/server/HttpTrafficHandler_Skip.java @@ -0,0 +1,15 @@ +/* + * + * * Copyright 2023 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package reactor.netty.http.server; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +// prevents this module from applying when reactor-netty 0.8.0 is present +@SkipIfPresent(originalName = "reactor.netty.http.server.HttpTrafficHandler") +class HttpTrafficHandler_Skip { +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java new file mode 100644 index 0000000000..3bd1c303c8 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitMany.java @@ -0,0 +1,37 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitMany { + + private List result = null; + private CompletableFuture> f; + + public AwaitMany() { + f = new CompletableFuture<>(); + } + + public List await() { + List s = Collections.emptyList(); + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void onError(Throwable t) { + result.add(t.getMessage()); + f.completeExceptionally(t); + } + + public void done(List result) { + System.out.println("AwaitMany done"); + this.result = result; + f.complete(result); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java new file mode 100644 index 0000000000..44434be273 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/AwaitSingle.java @@ -0,0 +1,32 @@ +package com.nr.instrumentation.reactor.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class AwaitSingle { + + private String result = null; + private CompletableFuture f; + + public AwaitSingle() { + f = new CompletableFuture(); + } + + public String getResult() { + return result; + } + + public String await() { + String s = null; + try { + s = f.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + return s; + } + + public void setResult(String s) { + result = s; + f.complete(result); + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java new file mode 100644 index 0000000000..acca8cd6f8 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/MonoCoreSubscriber.java @@ -0,0 +1,33 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class MonoCoreSubscriber implements CoreSubscriber { + + @Override + @Trace + public void onNext(String t) { + System.out.println("Received string for onNext: " + t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Received error for onError: " + t); + } + + @Override + @Trace + public void onComplete() { + System.out.println("Mono has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription var1) { + System.out.println("Mono was subscribed to by : " + var1); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java new file mode 100644 index 0000000000..b3a2dce717 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/SubscriptionConsumer.java @@ -0,0 +1,23 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; + +import java.util.function.Consumer; + +public class SubscriptionConsumer implements Consumer { + + @Override + @Trace + public void accept(Subscription subscription) { + pause(); + + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java new file mode 100644 index 0000000000..1476a1fa84 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestApplication.java @@ -0,0 +1,426 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.agent.introspec.*; +import com.newrelic.api.agent.Trace; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = "reactor.core") +public class TestApplication { + + + private static final String TRY_EMIT = "Java/reactor.core.publisher.SinkOneSerialized/tryEmitValue"; + private static final String TRY_EMIT2 = "Java/reactor.core.publisher.SinkOneMulticast/tryEmitValue"; + private static final String MONO_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onNext"; + private static final String MONO_COMPLETE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onComplete"; + private static final String MONO_SUBSCRIBE = "Custom/com.nr.instrumentation.reactor.test.TestMonoCoreSubscriber/onSubscribe"; + private static final String BEST_EFFORT_NEXT = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitNext"; + private static final String SERIALIZED_NEXT = "Java/reactor.core.publisher.SinkManySerialized/tryEmitNext"; + private static final String SUBSCRIBER_NEXT = "Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"; + private static final String SERIALIZED_COMPLETE = "Java/reactor.core.publisher.SinkManySerialized/tryEmitComplete"; + private static final String BEST_EFFORT_COMPLETE = "Java/reactor.core.publisher.SinkManyBestEffort/tryEmitComplete"; + private static final String txn1 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoSub"; + private static final String txn2 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testMonoPub"; + private static final String txn3 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxSub"; + private static final String txn4 = "OtherTransaction/Custom/com.nr.instrumentation.reactor.test.TestApplication/testFluxPub"; + private static final String WRAPPER = "Java/com.nr.instrumentation.reactor.NRRunnableWrapper/run"; + private static final String[] fluxArray = {"Message 1", "Message 2", "Message 3", "Message 4"}; + + @Test + public void doSinkOneTest() { + testSinkOne(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(TRY_EMIT)); + Assert.assertTrue(names.contains(TRY_EMIT2)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + } + + @Trace(dispatcher = true) + public void testSinkOne() { + + Sinks.One sink = Sinks.one(); + Mono mono = sink.asMono(); + + AwaitSingle awaitSingle = new AwaitSingle(); + mono.subscribe(new TestMonoCoreSubscriber(awaitSingle)); + + Schedulers.single().schedule(() -> { + Sinks.EmitResult result = sink.tryEmitValue("Hello"); + if(result.isFailure()) { + System.out.println("EmitResult threw an error: "+ result); + } + }); + + String result = awaitSingle.await(); + System.out.println("Await Single result : " + result); + } + + @Test + public void doTestSinkMany() { + testSinkMany(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + String txnName = txnNames.iterator().next(); + + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(BEST_EFFORT_NEXT)); + Assert.assertTrue(names.contains(SERIALIZED_NEXT)); + Assert.assertTrue(names.contains(SUBSCRIBER_NEXT)); + Assert.assertTrue(names.contains(BEST_EFFORT_COMPLETE)); + Assert.assertTrue(names.contains(SERIALIZED_COMPLETE)); + + TracedMetricData metric = metrics.get(BEST_EFFORT_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(SUBSCRIBER_NEXT); + Assert.assertEquals(5, metric.getCallCount()); + + metric = metrics.get(BEST_EFFORT_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + metric = metrics.get(SERIALIZED_COMPLETE); + Assert.assertEquals(1, metric.getCallCount()); + + } + + @Trace(dispatcher = true) + public void testSinkMany() { + Sinks.Many hotSource = Sinks.many().multicast().directBestEffort(); + + Flux flux = hotSource.asFlux(); + AwaitMany awaitMany = new AwaitMany(); + flux.subscribe(new TestFluxCoreSubscriber(awaitMany, 5)); + + Scheduler scheduler = Schedulers.single(); + String[] colors = new String[]{"Red", "Green", "Blue", "Pink", "Purple"}; + for(String color : colors) { + CompletableFuture completableFuture = new CompletableFuture<>(); + scheduler.schedule(() -> { + hotSource.emitNext(color, Sinks.EmitFailureHandler.FAIL_FAST); + completableFuture.complete(true); + }, 100, TimeUnit.MILLISECONDS); + + try { + Boolean result = completableFuture.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + + } + hotSource.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + List results = awaitMany.await(); + System.out.println("Await Many result : " + results); + } + + @Test + public void doMonoSubscribeOnTest() { + testMonoSub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transactions", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn1); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn1); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn1); + for (TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the subscribing occurs on another thread + */ + for (TraceSegment child : children) { + if (child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if (initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if (attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + break; + } + } + Assert.assertTrue(passes); + + } + } + + @Test + public void doMonoPublishOnTest() { + testMonoPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn2); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains(MONO_NEXT)); + Assert.assertTrue(names.contains(MONO_COMPLETE)); + + Collection traces = introspector.getTransactionTracesForTransaction(txn2); + for(TransactionTrace transactionTrace : traces) { + TraceSegment initialSegment = transactionTrace.getInitialTraceSegment(); + List children = initialSegment.getChildren(); + boolean passes = false; + /* + Assure that the publishing occurs on another thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + Map initialAttributes = initialSegment.getTracerAttributes(); + int initialThread; + if(initialAttributes.containsKey("thread.id")) { + initialThread = Integer.parseInt(initialAttributes.get("thread.id").toString()); + } else { + initialThread = -1; + } + Map attributes = child.getTracerAttributes(); + int childThread; + if(attributes.containsKey("thread.id")) { + childThread = Integer.parseInt(attributes.get("thread.id").toString()); + } else { + childThread = -1; + } + Assert.assertTrue(initialThread != childThread); + passes = true; + } + } + Assert.assertTrue(passes); + + } + + } + + @Test + public void doFluxPublishOnTest() { + testFluxPub(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn4); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn4); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Assert.assertTrue(names.contains("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext")); + TracedMetricData metricData = metrics.get("Custom/com.nr.instrumentation.reactor.test.TestFluxCoreSubscriber/onNext"); + Assert.assertNotNull(metricData); + Assert.assertEquals(4, metricData.getCallCount()); + + } + + @Test + public void doFluxSubscribeOnTest() { + testFluxSub(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + + Collection txnNames = introspector.getTransactionNames(); + boolean contains = txnNames.contains(txn3); // & txnNames.contains(txn2); + Assert.assertTrue(contains); + + Map metrics = introspector.getMetricsForTransaction(txn3); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + } + + @Test + public void doScheduleTest() { + testScheduler(); + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + int finishedTransactionCount = introspector.getFinishedTransactionCount(5000); + Assert.assertEquals("Expected one transaction", 1, finishedTransactionCount); + String txnName = introspector.getTransactionNames().iterator().next(); + Map metrics = introspector.getMetricsForTransaction(txnName); + Set names = metrics.keySet(); + Assert.assertTrue(names.contains(WRAPPER)); + Collection traces = introspector.getTransactionTracesForTransaction(txnName); + TransactionTrace transactionTrace = traces.iterator().next(); + Assert.assertNotNull(transactionTrace); + TraceSegment initial = transactionTrace.getInitialTraceSegment(); + Assert.assertNotNull(initial); + List children = initial.getChildren(); + boolean passes = false; + /* + Ensure that execution is dispatched to another thread and that Mono actions occur on that thread + */ + for(TraceSegment child : children) { + if(child.getName().equals(WRAPPER)) { + List wrapperChildren = child.getChildren(); + Set childNames = new HashSet<>(); + TraceSegment subscribeTrace = null; + for(TraceSegment wrapperChild : wrapperChildren) { + String childName = wrapperChild.getName(); + childNames.add(wrapperChild.getName()); + if(childName.equals(MONO_SUBSCRIBE)) subscribeTrace = wrapperChild; + } + Assert.assertTrue(childNames.contains(MONO_SUBSCRIBE)); + Assert.assertNotNull(subscribeTrace); + List subscriberChildren = subscribeTrace.getChildren(); + childNames.clear(); + for(TraceSegment subscriberChild : subscriberChildren) { + String childName = subscriberChild.getName(); + childNames.add(subscriberChild.getName()); + } + Assert.assertTrue(childNames.contains(MONO_NEXT)); + Assert.assertTrue(childNames.contains(MONO_COMPLETE)); + } + } + } + + + @Trace(dispatcher = true) + public void testScheduler() { + System.out.println("Enter testScheduler"); + Mono mono = getStringMono(); + AwaitSingle await = new AwaitSingle(); + Scheduler scheduler = Schedulers.single(); + scheduler.schedule(() -> { + mono.subscribe(new TestMonoCoreSubscriber(await)); + }); + String result = await.await(); + System.out.println("TestScheduler result: " + result); + + } + + @Trace(dispatcher = true) + public void testMonoPub() { + System.out.println("Enter testMonoPub"); + AwaitSingle await = new AwaitSingle(); + Mono mono = getStringMono(); + + mono.publishOn(Schedulers.single()).subscribe(new TestMonoCoreSubscriber(await)); + await.await(); + + System.out.println("Exit testMonoPub with result: " + await.getResult()); + + } + + @Trace(dispatcher = true) + public void testMonoSub() { + System.out.println("Enter testMonoSub"); + AwaitSingle await = new AwaitSingle(); + + Mono mono = getStringMono().subscribeOn(Schedulers.single()).doOnSubscribe(new SubscriptionConsumer()); + + mono.subscribe(new TestMonoCoreSubscriber(await)); + String result = await.await(); + System.out.println("Exit testMonoSub with result: " + result); + + } + + @Trace(dispatcher = true) + public void testFluxSub() { + System.out.println("Enter testFluxSub"); + + Flux flux = getStringFlux().subscribeOn(Schedulers.single()); + + flux.subscribe(this::doSubscribeAction); + + List list = flux.collectList().block(); + System.out.println("Exit testFluxSub with result: " + list); + } + + @Trace(dispatcher = true) + public void testFluxPub() { + System.out.println("Enter testFluxPub"); + Flux flux = getStringFlux().publishOn(Schedulers.single()); + AwaitMany await = new AwaitMany(); + + flux.subscribe(new TestFluxCoreSubscriber(await,4)); + + List list = await.await(); + System.out.println("Exit testFluxPub with result: "+ list); + + } + + + public Flux getStringFlux() { + return Flux.fromArray(fluxArray); + } + + public Mono getStringMono() { + + + return Mono.fromCallable(() -> { + try { + Thread.sleep(100L); + } catch(Exception ignored) { + + } + return "hello"; + }); + } + + @Trace + public void doSubscribeAction(String s) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Result is "+s); + } + + +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java new file mode 100644 index 0000000000..0d87eb60d0 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestFluxCoreSubscriber.java @@ -0,0 +1,52 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +import java.util.ArrayList; +import java.util.List; + +public class TestFluxCoreSubscriber implements CoreSubscriber { + + private AwaitMany await = null; + List result = null; + private int numberOfItems = 0; + public TestFluxCoreSubscriber(AwaitMany a, int numberOfItems) { + result = new ArrayList<>(); + await = a; + this.numberOfItems = numberOfItems; + } + + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result.add(t); + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: " + t.getMessage()); + if (await != null) { + List result = new ArrayList<>(); + result.add(t.getMessage()); + await.done(result); + } + + } + + @Override + @Trace + public void onComplete() { + await.done(result); + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(numberOfItems); + } + +} diff --git a/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java new file mode 100644 index 0000000000..0648030287 --- /dev/null +++ b/instrumentation/reactor-core-3.5.0/src/test/java/com/nr/instrumentation/reactor/test/TestMonoCoreSubscriber.java @@ -0,0 +1,50 @@ +package com.nr.instrumentation.reactor.test; + +import com.newrelic.api.agent.Trace; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class TestMonoCoreSubscriber implements CoreSubscriber { + + private AwaitSingle await = null; + String result = null; + + public TestMonoCoreSubscriber(AwaitSingle a) { + await = a; + } + + @Override + @Trace + public void onNext(String t) { + System.out.println("call to onNext with string: " + t); + result = t; + } + + @Override + @Trace + public void onError(Throwable t) { + System.out.println("Object has error: "+t.getMessage()); + if(await != null) { + await.setResult(t.getMessage()); + } + + } + + @Override + @Trace + public void onComplete() { + if(await != null) { + synchronized(await) { + await.setResult(result); + } + } + System.out.println("Object has completed"); + } + + @Override + @Trace + public void onSubscribe(Subscription s) { + s.request(1); + } + +} diff --git a/settings.gradle b/settings.gradle index f8a10508f9..8067d6dd3b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -352,7 +352,11 @@ include 'instrumentation:r2dbc-postgresql-0.9.2' include 'instrumentation:r2dbc-mssql' include 'instrumentation:rabbit-amqp-2.7.0' include 'instrumentation:rabbit-amqp-5.0.0' -include 'instrumentation:reactor-3.3.0' +include 'instrumentation:reactor-core-3.1.0' +include 'instrumentation:reactor-core-3.3.0' +include 'instrumentation:reactor-core-3.4.0' +include 'instrumentation:reactor-core-3.4.10' +include 'instrumentation:reactor-core-3.5.0' include 'instrumentation:resin-3' include 'instrumentation:resin-4' include 'instrumentation:resin-jmx'