diff --git a/Kotlin-Coroutines_1.2/build.gradle b/Kotlin-Coroutines_1.2/build.gradle index 985c48c..ef1f78d 100644 --- a/Kotlin-Coroutines_1.2/build.gradle +++ b/Kotlin-Coroutines_1.2/build.gradle @@ -23,7 +23,7 @@ jar { verifyInstrumentation { passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.2.0,1.4.0)' - passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.3.9,1.4.0)' + passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.2.0,1.4.0)' excludeRegex '.*SNAPSHOT' excludeRegex '.*alpha' excludeRegex '.*-eap-.*' diff --git a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java deleted file mode 100644 index c516b7c..0000000 --- a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.newrelic.instrumentation.kotlin.coroutines; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Trace; - -import kotlin.jvm.functions.Function1; - -public class NRFunction1Wrapper implements Function1 { - - private Function1 delegate = null; - private String name = null; - private static boolean isTransformed = false; - - public NRFunction1Wrapper(Function1 d, String n) { - delegate = d; - name = n; - if(!isTransformed) { - isTransformed = true; - AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); - } - } - - @Override - @Trace(dispatcher=true) - public R invoke(P1 p1) { - if(name != null) NewRelic.getAgent().getTracedMethod().setMetricName("Custom","WrappedSuspend",name); - if(delegate != null) { - return delegate.invoke(p1); - } - return null; - } - -} diff --git a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java index 7838ae0..5919ee3 100644 --- a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java +++ b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java @@ -27,58 +27,69 @@ public NRFunction2Wrapper(Function2 d,String n) { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - @Trace(async=true) + @Trace(async=true, excludeFromTransactionTrace = true) public R invoke(P1 p1, P2 p2) { - String nameStr = null; - boolean linked = false; - if(p1 instanceof CoroutineContext) { - CoroutineContext ctx = (CoroutineContext)p1; - nameStr = Utils.getCoroutineName(ctx); - Token token = Utils.getToken(ctx); - if(token != null) { - token.link(); - linked = true; - } - } - if(p1 instanceof CoroutineScope) { - CoroutineScope scope = (CoroutineScope)p1; - nameStr = Utils.getCoroutineName(scope.getCoroutineContext()); - if (!linked) { - Token token = Utils.getToken(scope.getCoroutineContext()); + + boolean isUndispatched = p1.getClass().getName().equals("kotlinx.coroutines.UndispatchedCoroutine") || p2.getClass().getName().equals("kotlinx.coroutines.UndispatchedCoroutine"); + if (!isUndispatched) { + String nameStr = null; + boolean linked = false; + if (p1 instanceof CoroutineContext) { + CoroutineContext ctx = (CoroutineContext) p1; + nameStr = Utils.getCoroutineName(ctx); + Token token = Utils.getToken(ctx); if (token != null) { token.link(); linked = true; - } + } } - } - if(p2 instanceof Continuation) { - Continuation continuation = (Continuation)p2; - if(nameStr == null) nameStr = Utils.getCoroutineName(continuation.getContext(), continuation); - if(nameStr == null || nameStr.equals(Utils.CREATEMETHOD1) || nameStr.equals(Utils.CREATEMETHOD2)) nameStr = name; - - if(!linked) { - Token token = Utils.getToken(continuation.getContext()); - if (token != null) { - token.link(); - linked = true; - } + if (p1 instanceof CoroutineScope) { + CoroutineScope scope = (CoroutineScope) p1; + nameStr = Utils.getCoroutineName(scope.getCoroutineContext()); + if (!linked) { + Token token = Utils.getToken(scope.getCoroutineContext()); + if (token != null) { + token.link(); + linked = true; + } + } } - - if (!Utils.ignoreContinuation(name) && !Utils.ignoreContinuation(continuation.getClass(), continuation.getContext())) { - NRContinuationWrapper wrapper = new NRContinuationWrapper(continuation, nameStr); - p2 = (P2) wrapper; + if (p2 instanceof Continuation) { + Continuation continuation = (Continuation) p2; + if (nameStr == null) + nameStr = Utils.getCoroutineName(continuation.getContext(), continuation); + if (nameStr == null || nameStr.equals(Utils.CREATEMETHOD1) || nameStr.equals(Utils.CREATEMETHOD2)) + nameStr = name; + + if (!linked) { + Token token = Utils.getToken(continuation.getContext()); + if (token != null) { + token.link(); + linked = true; + } + } + + if (!Utils.ignoreContinuation(name) + && !Utils.ignoreContinuation(continuation.getClass(), continuation.getContext())) { + NRContinuationWrapper wrapper = new NRContinuationWrapper(continuation, nameStr); + p2 = (P2) wrapper; + } } - } - if(nameStr == null) { - nameStr = name; - } - if(nameStr != null) { - NewRelic.getAgent().getTracedMethod().setMetricName("Custom","WrappedSuspend",nameStr); + if (nameStr == null) { + nameStr = name; + } + if (nameStr != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom", "WrappedSuspend", nameStr); + } } if(delegate != null) { return delegate.invoke(p1, p2); } return null; } + + public void markUndispatched() { + + } } diff --git a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java new file mode 100644 index 0000000..80c6a9f --- /dev/null +++ b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java @@ -0,0 +1,34 @@ +package com.newrelic.instrumentation.kotlin.coroutines; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnable implements Runnable { + + private Runnable delegate = null; + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnable(Runnable r,Token t) { + if(!isTransformed) { + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + isTransformed = true; + } + delegate = r; + token = t; + } + + @Override + @Trace(async = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java index 01fafda..5c9eba5 100644 --- a/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java +++ b/Kotlin-Coroutines_1.2/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; -import java.util.logging.Level; import com.newrelic.agent.config.AgentConfig; import com.newrelic.agent.config.AgentConfigListener; @@ -14,8 +13,10 @@ import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.jvm.internal.SuspendLambda; import kotlinx.coroutines.AbstractCoroutine; import kotlinx.coroutines.CoroutineName; +import kotlinx.coroutines.DispatchedTask; public class Utils implements AgentConfigListener { @@ -37,6 +38,24 @@ public class Utils implements AgentConfigListener { loadConfig(config); } + public static NRRunnable getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnable) { + return null; + } + if(r instanceof DispatchedTask) { + return null; + } + + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null && t.isActive()) { + return new NRRunnable(r, t); + } else if(t != null) { + t.expire(); + t = null; + } + return null; + } + private static void loadConfig(Config config) { String ignores = config.getValue(SUSPENDSIGNORECONFIG); if (ignores != null && !ignores.isEmpty()) { @@ -44,7 +63,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore suspend: {0}", token); ignoredSuspends.add(token); } } @@ -55,7 +73,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore continuation: {0}", token); ignoredContinuations.add(token); } } @@ -66,7 +83,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore dispatched continuation: {0}", token); ignoredDispatchs.add(token); } } @@ -118,7 +134,7 @@ public static boolean ignoreDispatched(Class dispatched, CoroutineContext con } public static boolean ignoreSuspend(Class suspend, CoroutineContext context) { - + String classname = suspend.getName(); if(ignoredSuspends.contains(classname)) return true; @@ -183,6 +199,9 @@ public static String getCoroutineName(CoroutineContext context, Continuation if(continuation instanceof AbstractCoroutine) { return ((AbstractCoroutine)continuation).nameString$kotlinx_coroutines_core(); } + if(continuation instanceof SuspendLambda) { + return ((SuspendLambda)continuation).toString(); + } return getCoroutineName(context,continuation.getClass()); } diff --git a/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/BuildersKt.java b/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/BuildersKt.java index 7f8d47f..bfd05d1 100644 --- a/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/BuildersKt.java +++ b/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/BuildersKt.java @@ -39,7 +39,7 @@ public static final Deferred async(CoroutineScope scope, CoroutineContext } if(name == null) name = block.getClass().getName(); NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","async",name); - if(!Utils.ignoreSuspend(block.getClass(),context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { + if(cStart != CoroutineStart.UNDISPATCHED && !Utils.ignoreSuspend(block.getClass(),context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { NRCoroutineToken nrContextToken = Utils.setToken(context); if(nrContextToken != null) { @@ -67,13 +67,16 @@ public static final Object invoke(CoroutineDispatcher dispatcher, Function2< @Trace public static final kotlinx.coroutines.Job launch(CoroutineScope scope, CoroutineContext context, CoroutineStart cStart, Function2, ? extends Object> block) { + String name = Utils.getCoroutineName(context); if(name == null) { name = Utils.getCoroutineName(scope.getCoroutineContext()); } if(name == null) name = block.getClass().getName(); NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","launch",name); - if(!Utils.ignoreSuspend(block.getClass(), context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { + boolean check1 = Utils.ignoreSuspend(block.getClass(), context); + boolean check2 = Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext()); + if(cStart != CoroutineStart.UNDISPATCHED && !check1 && !check2) { NRCoroutineToken nrContextToken = Utils.setToken(context); if(nrContextToken != null) { context = context.plus(nrContextToken); diff --git a/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java b/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java new file mode 100644 index 0000000..977c564 --- /dev/null +++ b/Kotlin-Coroutines_1.2/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java @@ -0,0 +1,24 @@ +package kotlinx.coroutines; + +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.newrelic.instrumentation.kotlin.coroutines.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines.Utils; + +import kotlin.coroutines.CoroutineContext; + +@Weave(type = MatchType.BaseClass) +public abstract class CoroutineDispatcher { + + @Trace + public void dispatch(CoroutineContext ctx, Runnable r) { + NRRunnable wrapper = Utils.getRunnableWrapper(r); + if(wrapper != null) { + r = wrapper; + } + + Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.3/src/main/java/kotlinx/coroutines/DispatchedTask.java b/Kotlin-Coroutines_1.3/src/main/java/kotlinx/coroutines/DispatchedTask.java deleted file mode 100644 index aaa38fe..0000000 --- a/Kotlin-Coroutines_1.3/src/main/java/kotlinx/coroutines/DispatchedTask.java +++ /dev/null @@ -1,37 +0,0 @@ -package kotlinx.coroutines; - -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; - -@Weave -public abstract class DispatchedTask { - - @NewField - protected Token token = null; - - public DispatchedTask(int mode) { - if(token == null) { - Token t = NewRelic.getAgent().getTransaction().getToken(); - if(t != null && t.isActive()) { - token = t; - } else if(t != null) { - t.expire(); - t = null; - } - } - } - - @Trace(async=true) - public void run() { - if(token != null) { - token.linkAndExpire(); - token = null; - } - Weaver.callOriginal(); - } - -} diff --git a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java deleted file mode 100644 index c516b7c..0000000 --- a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction1Wrapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.newrelic.instrumentation.kotlin.coroutines; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import com.newrelic.api.agent.Trace; - -import kotlin.jvm.functions.Function1; - -public class NRFunction1Wrapper implements Function1 { - - private Function1 delegate = null; - private String name = null; - private static boolean isTransformed = false; - - public NRFunction1Wrapper(Function1 d, String n) { - delegate = d; - name = n; - if(!isTransformed) { - isTransformed = true; - AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); - } - } - - @Override - @Trace(dispatcher=true) - public R invoke(P1 p1) { - if(name != null) NewRelic.getAgent().getTracedMethod().setMetricName("Custom","WrappedSuspend",name); - if(delegate != null) { - return delegate.invoke(p1); - } - return null; - } - -} diff --git a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java index 7838ae0..5919ee3 100644 --- a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java +++ b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRFunction2Wrapper.java @@ -27,58 +27,69 @@ public NRFunction2Wrapper(Function2 d,String n) { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - @Trace(async=true) + @Trace(async=true, excludeFromTransactionTrace = true) public R invoke(P1 p1, P2 p2) { - String nameStr = null; - boolean linked = false; - if(p1 instanceof CoroutineContext) { - CoroutineContext ctx = (CoroutineContext)p1; - nameStr = Utils.getCoroutineName(ctx); - Token token = Utils.getToken(ctx); - if(token != null) { - token.link(); - linked = true; - } - } - if(p1 instanceof CoroutineScope) { - CoroutineScope scope = (CoroutineScope)p1; - nameStr = Utils.getCoroutineName(scope.getCoroutineContext()); - if (!linked) { - Token token = Utils.getToken(scope.getCoroutineContext()); + + boolean isUndispatched = p1.getClass().getName().equals("kotlinx.coroutines.UndispatchedCoroutine") || p2.getClass().getName().equals("kotlinx.coroutines.UndispatchedCoroutine"); + if (!isUndispatched) { + String nameStr = null; + boolean linked = false; + if (p1 instanceof CoroutineContext) { + CoroutineContext ctx = (CoroutineContext) p1; + nameStr = Utils.getCoroutineName(ctx); + Token token = Utils.getToken(ctx); if (token != null) { token.link(); linked = true; - } + } } - } - if(p2 instanceof Continuation) { - Continuation continuation = (Continuation)p2; - if(nameStr == null) nameStr = Utils.getCoroutineName(continuation.getContext(), continuation); - if(nameStr == null || nameStr.equals(Utils.CREATEMETHOD1) || nameStr.equals(Utils.CREATEMETHOD2)) nameStr = name; - - if(!linked) { - Token token = Utils.getToken(continuation.getContext()); - if (token != null) { - token.link(); - linked = true; - } + if (p1 instanceof CoroutineScope) { + CoroutineScope scope = (CoroutineScope) p1; + nameStr = Utils.getCoroutineName(scope.getCoroutineContext()); + if (!linked) { + Token token = Utils.getToken(scope.getCoroutineContext()); + if (token != null) { + token.link(); + linked = true; + } + } } - - if (!Utils.ignoreContinuation(name) && !Utils.ignoreContinuation(continuation.getClass(), continuation.getContext())) { - NRContinuationWrapper wrapper = new NRContinuationWrapper(continuation, nameStr); - p2 = (P2) wrapper; + if (p2 instanceof Continuation) { + Continuation continuation = (Continuation) p2; + if (nameStr == null) + nameStr = Utils.getCoroutineName(continuation.getContext(), continuation); + if (nameStr == null || nameStr.equals(Utils.CREATEMETHOD1) || nameStr.equals(Utils.CREATEMETHOD2)) + nameStr = name; + + if (!linked) { + Token token = Utils.getToken(continuation.getContext()); + if (token != null) { + token.link(); + linked = true; + } + } + + if (!Utils.ignoreContinuation(name) + && !Utils.ignoreContinuation(continuation.getClass(), continuation.getContext())) { + NRContinuationWrapper wrapper = new NRContinuationWrapper(continuation, nameStr); + p2 = (P2) wrapper; + } } - } - if(nameStr == null) { - nameStr = name; - } - if(nameStr != null) { - NewRelic.getAgent().getTracedMethod().setMetricName("Custom","WrappedSuspend",nameStr); + if (nameStr == null) { + nameStr = name; + } + if (nameStr != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom", "WrappedSuspend", nameStr); + } } if(delegate != null) { return delegate.invoke(p1, p2); } return null; } + + public void markUndispatched() { + + } } diff --git a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java new file mode 100644 index 0000000..80c6a9f --- /dev/null +++ b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/NRRunnable.java @@ -0,0 +1,34 @@ +package com.newrelic.instrumentation.kotlin.coroutines; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; + +public class NRRunnable implements Runnable { + + private Runnable delegate = null; + private Token token = null; + private static boolean isTransformed = false; + + public NRRunnable(Runnable r,Token t) { + if(!isTransformed) { + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + isTransformed = true; + } + delegate = r; + token = t; + } + + @Override + @Trace(async = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java index 01fafda..5c9eba5 100644 --- a/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java +++ b/Kotlin-Coroutines_1.4/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; -import java.util.logging.Level; import com.newrelic.agent.config.AgentConfig; import com.newrelic.agent.config.AgentConfigListener; @@ -14,8 +13,10 @@ import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.jvm.internal.SuspendLambda; import kotlinx.coroutines.AbstractCoroutine; import kotlinx.coroutines.CoroutineName; +import kotlinx.coroutines.DispatchedTask; public class Utils implements AgentConfigListener { @@ -37,6 +38,24 @@ public class Utils implements AgentConfigListener { loadConfig(config); } + public static NRRunnable getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnable) { + return null; + } + if(r instanceof DispatchedTask) { + return null; + } + + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null && t.isActive()) { + return new NRRunnable(r, t); + } else if(t != null) { + t.expire(); + t = null; + } + return null; + } + private static void loadConfig(Config config) { String ignores = config.getValue(SUSPENDSIGNORECONFIG); if (ignores != null && !ignores.isEmpty()) { @@ -44,7 +63,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore suspend: {0}", token); ignoredSuspends.add(token); } } @@ -55,7 +73,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore continuation: {0}", token); ignoredContinuations.add(token); } } @@ -66,7 +83,6 @@ private static void loadConfig(Config config) { while (st.hasMoreTokens()) { String token = st.nextToken(); if (token != null && !token.isEmpty()) { - NewRelic.getAgent().getLogger().log(Level.INFO, "will ignore dispatched continuation: {0}", token); ignoredDispatchs.add(token); } } @@ -118,7 +134,7 @@ public static boolean ignoreDispatched(Class dispatched, CoroutineContext con } public static boolean ignoreSuspend(Class suspend, CoroutineContext context) { - + String classname = suspend.getName(); if(ignoredSuspends.contains(classname)) return true; @@ -183,6 +199,9 @@ public static String getCoroutineName(CoroutineContext context, Continuation if(continuation instanceof AbstractCoroutine) { return ((AbstractCoroutine)continuation).nameString$kotlinx_coroutines_core(); } + if(continuation instanceof SuspendLambda) { + return ((SuspendLambda)continuation).toString(); + } return getCoroutineName(context,continuation.getClass()); } diff --git a/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/BuildersKt.java b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/BuildersKt.java index 7f8d47f..bfd05d1 100644 --- a/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/BuildersKt.java +++ b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/BuildersKt.java @@ -39,7 +39,7 @@ public static final Deferred async(CoroutineScope scope, CoroutineContext } if(name == null) name = block.getClass().getName(); NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","async",name); - if(!Utils.ignoreSuspend(block.getClass(),context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { + if(cStart != CoroutineStart.UNDISPATCHED && !Utils.ignoreSuspend(block.getClass(),context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { NRCoroutineToken nrContextToken = Utils.setToken(context); if(nrContextToken != null) { @@ -67,13 +67,16 @@ public static final Object invoke(CoroutineDispatcher dispatcher, Function2< @Trace public static final kotlinx.coroutines.Job launch(CoroutineScope scope, CoroutineContext context, CoroutineStart cStart, Function2, ? extends Object> block) { + String name = Utils.getCoroutineName(context); if(name == null) { name = Utils.getCoroutineName(scope.getCoroutineContext()); } if(name == null) name = block.getClass().getName(); NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","launch",name); - if(!Utils.ignoreSuspend(block.getClass(), context) && !Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext())) { + boolean check1 = Utils.ignoreSuspend(block.getClass(), context); + boolean check2 = Utils.ignoreSuspend(block.getClass(), scope.getCoroutineContext()); + if(cStart != CoroutineStart.UNDISPATCHED && !check1 && !check2) { NRCoroutineToken nrContextToken = Utils.setToken(context); if(nrContextToken != null) { context = context.plus(nrContextToken); diff --git a/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java new file mode 100644 index 0000000..977c564 --- /dev/null +++ b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java @@ -0,0 +1,24 @@ +package kotlinx.coroutines; + +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.newrelic.instrumentation.kotlin.coroutines.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines.Utils; + +import kotlin.coroutines.CoroutineContext; + +@Weave(type = MatchType.BaseClass) +public abstract class CoroutineDispatcher { + + @Trace + public void dispatch(CoroutineContext ctx, Runnable r) { + NRRunnable wrapper = Utils.getRunnableWrapper(r); + if(wrapper != null) { + r = wrapper; + } + + Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/DispatcherExecutor.java b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/DispatcherExecutor.java new file mode 100644 index 0000000..d9f59a9 --- /dev/null +++ b/Kotlin-Coroutines_1.4/src/main/java/kotlinx/coroutines/DispatcherExecutor.java @@ -0,0 +1,23 @@ +package kotlinx.coroutines; + +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.newrelic.instrumentation.kotlin.coroutines.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines.Utils; + +@Weave(type = MatchType.BaseClass) +abstract class DispatcherExecutor { + + @Trace + public void execute(Runnable r) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","Coroutines","DispatcherExecutor","execute"); + NRRunnable wrapper = Utils.getRunnableWrapper(r); + if(wrapper != null) { + r = wrapper; + } + Weaver.callOriginal(); + } +} diff --git a/README.md b/README.md index 89d325a..ea16596 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,7 @@ Provides instrumentation for Kotlin Coroutines. In particular it will trace the Kotlin-Coroutines-1.0 - all 1.0.x versions. Kotlin-Coroutines-1.1 - all 1.1.x versions. -Kotlin-Coroutines-1.2 - all 1.2.x versions. -Kotlin-Coroutines-1.3 - all 1.3.x versions. +Kotlin-Coroutines-1.2 - all 1.2.x and 1.3.x versions. Kotlin-Coroutines-1.4 - all 1.4.x and later versions. ## Installation @@ -34,6 +33,10 @@ In the New Relic Java Agent directory (directory containing newrelic.jar), creat Copy the jars into the extensions directory. Restart the application. +## Verification +The easiest way to see if the instrumentation has been loaded by the Java Agent is to look for the Java Agent UI's Metric Explorer and enter "supportability/weaveinstrumentation/loaded/com.newrelic.instrumentation.labs.kotlin-coroutines". If a metric is reported then the agent has loaded the instrumentation appropriate to the version of Kotlin Coroutines that you are using. +If no metric is displayed, then check for "supportability/weaveinstrumentation/skipped/com.newrelic.instrumentation.labs.kotlin-coroutines". If no metrics are displayed verify that the instrumentation jars have been deployed to the extensions directory and that the user id that runs the application has read access. + ## Getting Started After deployment of the instrumentation jars, you should be able to see the invocation of a coroutine from start to finish across any threads that it executes on. diff --git a/settings.gradle b/settings.gradle index 7f2e25e..50fc495 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,5 +4,3 @@ include 'Kotlin-Coroutines_1.1' include 'Kotlin-Coroutines_1.2' include 'Kotlin-Coroutines_1.4' include 'kroto-plus-coroutines' -include 'kotlin-coroutines-reactor' -include 'kotlin-coroutines-reactor'