diff --git a/Kotlin-Coroutines_1.4-nativemt/build.gradle b/Kotlin-Coroutines_1.4-nativemt/build.gradle new file mode 100644 index 0000000..519b6f5 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/build.gradle @@ -0,0 +1,34 @@ + + +apply plugin: 'java' +apply plugin: 'org.jetbrains.kotlin.jvm' + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1-native-mt") + + // New Relic Java Agent dependencies + implementation fileTree(include: ['*.jar'], dir: '../libs') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.labs.Kotlin-Coroutines_1.4-native-mt' + attributes 'Implementation-Vendor': 'New Relic Labs' + attributes 'Implementation-Vendor-Id': 'com.newrelic.labs' + attributes 'Implementation-Version': 2.0 + } +} + +verifyInstrumentation { +// passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.7.0)' +// passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.4.0,1.7.0)' + passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.5.1.native-mt,1.7.0)' + excludeRegex '.*SNAPSHOT' + excludeRegex '.*alpha' + excludeRegex '.*-eap-.*' + excludeRegex '.*-native-.*' + excludeRegex '.*-M[0-9]' + excludeRegex '.*-rc' + excludeRegex '.*-RC.*' + excludeRegex '.*-Beta' +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/DispatchedTaskIgnores.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/DispatchedTaskIgnores.java new file mode 100644 index 0000000..bfad762 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/DispatchedTaskIgnores.java @@ -0,0 +1,54 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; + +/* +* Used to ignore Dispatched Tasks that the user has configured to ignore +*/ +public class DispatchedTaskIgnores { + + private static final List ignoredTasks = new ArrayList<>(); + private static final String DISPATCHED_IGNORE_CONFIG = "Coroutines.ignores.dispatched"; + + static { + Config config = NewRelic.getAgent().getConfig(); + String ignores = config.getValue(DISPATCHED_IGNORE_CONFIG); + configure(ignores); + + } + + + public static boolean ignoreDispatchedTask(String dispatchedTask) { + return ignoredTasks.contains(dispatchedTask); + } + + public static void addIgnoredTasks(Collection toIgnore) { + ignoredTasks.addAll(toIgnore); + } + + public static void addIgnore(String ignore) { + if(!ignoredTasks.contains(ignore)) { + ignoredTasks.add(ignore); + NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore DispatchedTasks with continuation string {0}", ignore); + } + } + + public static void reset() { + ignoredTasks.clear(); + } + + protected static void configure(String result) { + if(result == null || result.isEmpty()) return; + String[] ignores = result.split(","); + for(String ignore : ignores) { + addIgnore(ignore); + } + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRContinuationWrapper.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRContinuationWrapper.java new file mode 100644 index 0000000..664f0e7 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRContinuationWrapper.java @@ -0,0 +1,56 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +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 kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import org.jetbrains.annotations.NotNull; + +/* +* Used to wrap a Continuation instance. Necessary to control which Continuations +* are tracked. Tracking all Continuations can effect performance. +* */ +public class NRContinuationWrapper implements Continuation { + + private Continuation delegate = null; + private String name = null; + private static boolean isTransformed = false; + + public NRContinuationWrapper(Continuation d, String n) { + delegate = d; + name = n; + if(!isTransformed) { + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + isTransformed = true; + } + } + + @NotNull + @Override + public CoroutineContext getContext() { + return delegate.getContext(); + } + + @Override + @Trace(async=true) + public void resumeWith(@NotNull Object p0) { + String contString = Utils.getContinuationString(delegate); + if(contString != null && !contString.isEmpty()) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",contString); + } else if(name != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",name); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",p0.getClass().getName()); + } + Token t = Utils.getToken(getContext()); + if(t != null) { + t.link(); + } + if(delegate != null) { + delegate.resumeWith(p0); + } + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayCancellableContinuation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayCancellableContinuation.java new file mode 100644 index 0000000..45dbabc --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayCancellableContinuation.java @@ -0,0 +1,146 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Segment; +import kotlin.Unit; +import kotlin.coroutines.CoroutineContext; +import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.CancellableContinuation; +import kotlinx.coroutines.CoroutineDispatcher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/* + * Used to wrap the tracking of a call to the Coroutine delay function if tracking is enabled + * Will report the delay as a segment + */ +public class NRDelayCancellableContinuation implements CancellableContinuation { + + private final CancellableContinuation delegate; + private Segment segment; + + public NRDelayCancellableContinuation(CancellableContinuation delegate, String type) { + this.delegate = delegate; + String name = Utils.getContinuationString(delegate); + segment = NewRelic.getAgent().getTransaction().startSegment(type); + segment.addCustomAttribute("CancellableContinuation", name); + } + + @Override + public void resumeWith(@NotNull Object o) { + if(segment != null) { + segment.end(); + segment = null; + } + if(delegate != null) { + delegate.resumeWith(o); + } + } + + @Override + public @NotNull CoroutineContext getContext() { + return delegate.getContext(); + } + + @Override + public boolean isActive() { + return delegate.isActive(); + } + + @Override + public boolean isCompleted() { + return delegate.isCompleted(); + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + + @Override + public @Nullable Object tryResume(T t, @Nullable Object o) { + if(segment != null) { + segment.end(); + segment = null; + } + return delegate.tryResume(t,o); + } + + @Override + public @Nullable Object tryResume(T t, @Nullable Object o, @Nullable Function1 function1) { + if(segment != null) { + segment.end(); + segment = null; + } + return delegate.tryResume(t,o, function1); + } + + @Override + public @Nullable Object tryResumeWithException(@NotNull Throwable throwable) { + NewRelic.noticeError(throwable); + if(segment != null) { + segment.end(); + segment = null; + } + return delegate.tryResumeWithException(throwable); + } + + @Override + public void completeResume(@NotNull Object o) { + if(segment != null) { + segment.end(); + segment = null; + } + delegate.completeResume(o); + } + + @Override + public void initCancellability() { + delegate.initCancellability(); + } + + @Override + public boolean cancel(@Nullable Throwable throwable) { + if(segment != null) { + segment.end(); + segment = null; + } + return delegate.cancel(throwable); + } + + @Override + public void invokeOnCancellation(@NotNull Function1 function1) { + if(segment != null) { + segment.end(); + segment = null; + } + delegate.invokeOnCancellation(function1); + } + + @Override + public void resumeUndispatched(@NotNull CoroutineDispatcher coroutineDispatcher, T t) { + if(segment != null) { + segment.end(); + segment = null; + } + delegate.resumeUndispatched(coroutineDispatcher, t); + } + + @Override + public void resumeUndispatchedWithException(@NotNull CoroutineDispatcher coroutineDispatcher, @NotNull Throwable throwable) { + if(segment != null) { + segment.end(); + segment = null; + } + delegate.resumeUndispatchedWithException(coroutineDispatcher, throwable); + } + + @Override + public void resume(T t, @Nullable Function1 function1) { + if(segment != null) { + segment.end(); + segment = null; + } + delegate.resume(t,function1); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayContinuation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayContinuation.java new file mode 100644 index 0000000..cce560a --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRDelayContinuation.java @@ -0,0 +1,38 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Segment; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.CancellableContinuation; +import org.jetbrains.annotations.NotNull; + +public class NRDelayContinuation implements Continuation { + + private final Continuation delegate; + private final Segment segment; + + public NRDelayContinuation(Continuation delegate, String type) { + this.delegate = delegate; + String name = Utils.getContinuationString(delegate); + segment = NewRelic.getAgent().getTransaction().startSegment(type); + segment.addCustomAttribute("Continuation", name); + boolean isCancellable = delegate instanceof CancellableContinuation; + segment.addCustomAttribute("isCancellable", isCancellable); + } + + @Override + public void resumeWith(@NotNull Object o) { + if(segment != null) { + segment.end(); + } + if(delegate != null) { + delegate.resumeWith(o); + } + } + + @Override + public @NotNull CoroutineContext getContext() { + return delegate.getContext(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1SuspendWrapper.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1SuspendWrapper.java new file mode 100644 index 0000000..e074a3a --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1SuspendWrapper.java @@ -0,0 +1,41 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.NewRelic; +import kotlin.coroutines.Continuation; +import kotlin.jvm.functions.Function1; + +/* +* Used to wrap a suspend function that was used as an input as a parameter to the +* Coroutine functions runBlocking, async, invoke, launch and withContext +*/ +public class NRFunction1SuspendWrapper implements Function1 { + + private Function1 delegate = null; + private static boolean isTransformed = false; + + public NRFunction1SuspendWrapper(Function1 d) { + delegate = d; + if(!isTransformed) { + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + isTransformed = true; + } + } + + @Override + public R invoke(P1 p1) { + if(p1 instanceof Continuation) { + Continuation cont = (Continuation)p1; + + String cont_string = Utils.getContinuationString(cont); + if(cont_string != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction",cont_string); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction","UnknownSource"); + + } + } + return delegate != null ? delegate.invoke(p1) : null; + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1Wrapper.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1Wrapper.java new file mode 100644 index 0000000..a1d838d --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction1Wrapper.java @@ -0,0 +1,35 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.agent.bridge.AgentBridge; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.jvm.internal.SuspendFunction; +import kotlin.jvm.functions.Function1; + +public class NRFunction1Wrapper implements Function1 { + + private Function1 delegate = null; + private static boolean isTransformed = false; + + public NRFunction1Wrapper(Function1 d) { + delegate = d; + if(!isTransformed) { + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + isTransformed = true; + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public R invoke(P1 p1) { + if(p1 instanceof Continuation && !(p1 instanceof SuspendFunction)) { + // wrap if needed + Continuation continuation = (Continuation) p1; + if(!(p1 instanceof NRContinuationWrapper) && Utils.continueWithContinuation(continuation)) { + NRContinuationWrapper wrapper = new NRContinuationWrapper<>((Continuation) p1, Utils.getContinuationString(continuation)); + p1 = (P1) wrapper; + } + } + return delegate != null ? delegate.invoke(p1) : null; + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction2SuspendWrapper.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction2SuspendWrapper.java new file mode 100644 index 0000000..820b941 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRFunction2SuspendWrapper.java @@ -0,0 +1,66 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.jvm.functions.Function2; +import kotlinx.coroutines.CoroutineScope; + +/* + * Used to wrap a suspend function that was used as an input as a parameter to the + * Coroutine functions runBlocking, async, invoke, launch and withContext + */ +public class NRFunction2SuspendWrapper implements Function2 { + + private Function2 delegate = null; + private static boolean isTransformed = false; + + public NRFunction2SuspendWrapper(Function2 d) { + delegate = d; + if(!isTransformed) { + isTransformed = true; + AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); + } + } + + @Override + public R invoke(S s, T t) { + + // set name + boolean name_set = false; + if(s instanceof CoroutineScope) { + CoroutineScope scope = (CoroutineScope)s; + CoroutineContext ctx = scope.getCoroutineContext(); + Token token = Utils.getToken(ctx); + if(token != null) { + token.link(); + } + CoroutineContext context = scope.getCoroutineContext(); + String coroutineName = Utils.getCoroutineName(context); + if(coroutineName != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction",coroutineName); + name_set = true; + } + + } + + if(!name_set && t instanceof Continuation) { + Continuation cont = (Continuation)t; + + String cont_string = Utils.getContinuationString(cont); + if(cont_string != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction",cont_string); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction","UnknownSource"); + + } + } + if(delegate != null) { + return delegate.invoke(s, t); + } + return null; + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRRunnable.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRRunnable.java new file mode 100644 index 0000000..88ab3eb --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/NRRunnable.java @@ -0,0 +1,50 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +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 kotlin.coroutines.Continuation; +import kotlinx.coroutines.DispatchedTask; + +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() { + boolean nameSet = false; + if(delegate != null && delegate instanceof DispatchedTask) { + DispatchedTask task = (DispatchedTask)delegate; + Continuation cont_delegate = task.getDelegate$kotlinx_coroutines_core(); + String cont_string = Utils.getContinuationString(cont_delegate); + if(cont_string == null) cont_string = cont_delegate.getClass().getName(); + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","DispatchedTask",cont_string); + nameSet = true; + } + if(!nameSet) { + String delegateType = delegate != null ? delegate.getClass().getName() : "null"; + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","AsyncRunnableWrapper",delegateType); + } + if(token != null) { + token.linkAndExpire(); + token = null; + } + if(delegate != null) { + delegate.run(); + } + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/Utils.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/Utils.java new file mode 100644 index 0000000..92bed4e --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_14/Utils.java @@ -0,0 +1,260 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import com.newrelic.agent.config.AgentConfig; +import com.newrelic.agent.config.AgentConfigListener; +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.jvm.internal.BaseContinuationImpl; +import kotlinx.coroutines.AbstractCoroutine; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.DispatchedTask; + +public class Utils implements AgentConfigListener { + + private static final List ignoredContinuations = new ArrayList(); + private static final List ignoredScopes = new ArrayList<>(); + private static final String CONTIGNORECONFIG = "Coroutines.ignores.continuations"; + private static final String SCOPESIGNORECONFIG = "Coroutines.ignores.scopes"; + private static final String DISPATCHEDIGNORECONFIG = "Coroutines.ignores.dispatched"; + private static final String DELAYED_ENABLED_CONFIG = "Coroutines.delayed.enabled"; + + public static final String CREATEMETHOD1 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4"; + public static final String CREATEMETHOD2 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3"; + private static final Utils INSTANCE = new Utils(); + private static final String CONT_LOC = "Continuation at"; + public static boolean DELAYED_ENABLED = true; + + static { + ServiceFactory.getConfigService().addIAgentConfigListener(INSTANCE); + Config config = NewRelic.getAgent().getConfig(); + loadConfig(config); + ignoredContinuations.add(CREATEMETHOD1); + ignoredContinuations.add(CREATEMETHOD2); + Object value = config.getValue(DELAYED_ENABLED_CONFIG); + if(value != null) { + if(value instanceof Boolean) { + DELAYED_ENABLED = (Boolean)value; + } else { + DELAYED_ENABLED = Boolean.valueOf(value.toString()); + } + } + + } + + public static NRRunnable getRunnableWrapper(Runnable r) { + if(r instanceof NRRunnable) { + return null; + } + if(r instanceof DispatchedTask) { + DispatchedTask task = (DispatchedTask)r; + Continuation cont = task.getDelegate$kotlinx_coroutines_core(); + if(cont != null) { + String cont_string = getContinuationString(cont); + if(cont_string != null && DispatchedTaskIgnores.ignoreDispatchedTask(cont_string)) { + 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(CONTIGNORECONFIG); + NewRelic.getAgent().getLogger().log(Level.FINE, "Value of {0}: {1}", CONTIGNORECONFIG, ignores); + if (ignores != null && !ignores.isEmpty()) { + ignoredContinuations.clear(); + String[] ignoresList = ignores.split(","); + + for(String ignore : ignoresList) { + if (!ignoredContinuations.contains(ignore)) { + ignoredContinuations.add(ignore); + NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore Continuations named {0}", ignore); + } + } + } else if(!ignoredContinuations.isEmpty()) { + ignoredContinuations.clear(); + } + ignores = config.getValue(DISPATCHEDIGNORECONFIG); + NewRelic.getAgent().getLogger().log(Level.FINE, "Value of {0}: {1}", DISPATCHEDIGNORECONFIG, ignores); + DispatchedTaskIgnores.reset(); + if (ignores != null && !ignores.isEmpty()) { + DispatchedTaskIgnores.configure(ignores); + } + ignores = config.getValue(SCOPESIGNORECONFIG); + if (ignores != null && !ignores.isEmpty()) { + ignoredScopes.clear(); + String[] ignoresList = ignores.split(","); + + for(String ignore : ignoresList) { + if (!ignoredScopes.contains(ignore)) { + ignoredScopes.add(ignore); + NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore CoroutineScopes named {0}", ignore); + } + } + } else if(!ignoredScopes.isEmpty()) { + ignoredScopes.clear(); + } + + } + + public static boolean ignoreScope(CoroutineScope scope) { + CoroutineContext ctx = scope.getCoroutineContext(); + String name = getCoroutineName(ctx); + String className = scope.getClass().getName(); + return ignoreScope(className) || ignoreScope(name); + } + + public static boolean ignoreScope(String coroutineScope) { + return ignoredScopes.contains(coroutineScope); + } + + public static boolean ignoreContinuation(String cont_string) { + return ignoredContinuations.contains(cont_string); + } + + /* + * Allows certain Coroutine scopes to be ignored + * coroutineScope can be a Coroutine name or CoroutineScope class name + */ + public static boolean continueWithScope(CoroutineScope scope) { + CoroutineContext ctx = scope.getCoroutineContext(); + String name = getCoroutineName(ctx); + String className = scope.getClass().getName(); + return continueWithScope(className) && continueWithScope(name); + } + + /* + * Allows certain Coroutine scopes to be ignored + * coroutineScope can be a Coroutine name or CoroutineScope class name + */ + public static boolean continueWithScope(String coroutineScope) { + for(String ignoredScope : ignoredScopes) { + if(coroutineScope.matches(ignoredScope)) { + return false; + } + } + return !ignoredScopes.contains(coroutineScope); + } + + public static boolean continueWithContinuation(Continuation continuation) { + /* + * Don't trace internal Coroutines Continuations + */ + String className = continuation.getClass().getName(); + if(className.startsWith("kotlin")) return false; + + /* + * Get the continuation string and check if it should be ignored + */ + String cont_string = getContinuationString(continuation); + if(cont_string == null) { return false; } + + for(String ignored : ignoredContinuations) { + if(cont_string.matches(ignored)) { + return false; + } + } + return !ignoredContinuations.contains(cont_string); + } + + + + public static String sub = "createCoroutineFromSuspendFunction"; + + public static void setToken(CoroutineContext context) { + TokenContext tokenContext = NRTokenContextKt.getTokenContextOrNull(context); + if (tokenContext == null) { + Token t = NewRelic.getAgent().getTransaction().getToken(); + if(t != null && t.isActive()) { + NRTokenContextKt.addTokenContext(context, t); + } else if(t != null) { + t.expire(); + t = null; + } + } + } + + public static Token getToken(CoroutineContext context) { + TokenContext tokenContext = NRTokenContextKt.getTokenContextOrNull(context); + if(tokenContext != null) { + return tokenContext.getToken(); + } + return null; + } + + public static void expireToken(CoroutineContext context) { + TokenContext tokenContext = NRTokenContextKt.getTokenContextOrNull(context); + if(tokenContext != null) { + Token token = tokenContext.getToken(); + token.expire(); + NRTokenContextKt.removeTokenContext(context); + } + } + + public static String getCoroutineName(CoroutineContext context, Continuation continuation) { + if(continuation instanceof AbstractCoroutine) { + return ((AbstractCoroutine)continuation).nameString$kotlinx_coroutines_core(); + } + if(continuation instanceof BaseContinuationImpl) { + return ((BaseContinuationImpl)continuation).toString(); + } + return null; + } + + public static String getCoroutineName(CoroutineContext context) { + return CoroutineNameUtilsKt.getCoroutineName(context); + } + + @Override + public void configChanged(String appName, AgentConfig agentConfig) { + loadConfig(agentConfig); + Object value = agentConfig.getValue(DELAYED_ENABLED_CONFIG); + if(value != null) { + if(value instanceof Boolean) { + DELAYED_ENABLED = (Boolean)value; + } else { + DELAYED_ENABLED = Boolean.valueOf(value.toString()); + } + } + } + + public static String getContinuationString(Continuation continuation) { + String contString = continuation.toString(); + + if(contString.equals(CREATEMETHOD1) || contString.equals(CREATEMETHOD2)) { + return sub; + } + + if(contString.startsWith(CONT_LOC)) { + return contString; + } + + if(continuation instanceof AbstractCoroutine) { + return ((AbstractCoroutine)continuation).nameString$kotlinx_coroutines_core(); + } + + int index = contString.indexOf('@'); + if(index > -1) { + return contString.substring(0, index); + } + + return null; + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlin/coroutines/ContinuationKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlin/coroutines/ContinuationKt_Instrumentation.java new file mode 100644 index 0000000..a98077a --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlin/coroutines/ContinuationKt_Instrumentation.java @@ -0,0 +1,32 @@ +package kotlin.coroutines; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction1SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction2SuspendWrapper; + +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; + +@Weave(originalName = "kotlin.coroutines.ContinuationKt") +public abstract class ContinuationKt_Instrumentation { + + @Trace + public static void startCoroutine(Function1, ?> f, Continuation cont ) { + if(!(f instanceof NRFunction1SuspendWrapper)) { + f = new NRFunction1SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + @Trace + public static void startCoroutine(Function2, ?> f, R receiver, Continuation cont) { + if(!(f instanceof NRFunction2SuspendWrapper)) { + f = new NRFunction2SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AbstractCoroutine_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AbstractCoroutine_Instrumentation.java new file mode 100644 index 0000000..f758b30 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AbstractCoroutine_Instrumentation.java @@ -0,0 +1,58 @@ +package kotlinx.coroutines; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.TracedMethod; +import com.newrelic.api.agent.TransactionNamePriority; +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_14.NRFunction2SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.jvm.functions.Function2; + +@Weave(type=MatchType.BaseClass, originalName = "kotlinx.coroutines.AbstractCoroutine") +public abstract class AbstractCoroutine_Instrumentation { + + public abstract CoroutineContext getContext(); + public abstract String nameString$kotlinx_coroutines_core(); + + protected void onCompleted(T value) { + Utils.expireToken(getContext()); + Weaver.callOriginal(); + } + + protected void onCancelled(Throwable t, boolean b) { + Utils.expireToken(getContext()); + Weaver.callOriginal(); + } + + public void handleOnCompletionException$kotlinx_coroutines_core(Throwable t) { + Weaver.callOriginal(); + } + + @Trace + public void start(CoroutineStart start, R receiver, Function2, ?> block) { + if(!(block instanceof NRFunction2SuspendWrapper)) { + block = new NRFunction2SuspendWrapper<>(block); + } + String ctxName = Utils.getCoroutineName(getContext()); + String name = ctxName != null ? ctxName : nameString$kotlinx_coroutines_core(); + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + traced.addCustomAttribute("Coroutine-Name", name); + traced.addCustomAttribute("Block", block.toString()); + + if(name != null && !name.isEmpty()) { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, false, "Coroutine", "Kotlin","Coroutine",name); + traced.setMetricName("Custom","Kotlin","Coroutines",getClass().getSimpleName(),"start",name); + } else { + NewRelic.getAgent().getTransaction().setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, false, "Coroutine", "Kotlin","Coroutine"); + } + + Weaver.callOriginal(); + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AwaitAll_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AwaitAll_Instrumentation.java new file mode 100644 index 0000000..9ef878e --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/AwaitAll_Instrumentation.java @@ -0,0 +1,21 @@ +package kotlinx.coroutines; + +import java.util.List; + +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 kotlin.coroutines.Continuation; + +@Weave(originalName = "kotlinx.coroutines.AwaitAll") +abstract class AwaitAll_Instrumentation { + + @Trace + public Object await(Continuation> cont) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","AwaitAll","await"); + return Weaver.callOriginal(); + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java new file mode 100644 index 0000000..c43463c --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java @@ -0,0 +1,138 @@ +package kotlinx.coroutines; + +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.newrelic.instrumentation.kotlin.coroutines_14.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction2SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.Unit; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.jvm.internal.SuspendFunction; +import kotlin.jvm.functions.Function2; + +@SuppressWarnings({ "unchecked", "rawtypes" }) +@Weave(originalName = "kotlinx.coroutines.BuildersKt") +public class BuildersKt_Instrumentation { + + @Trace(dispatcher = true) + public static T runBlocking(CoroutineContext context, Function2, ?> block) { + String name = Utils.getCoroutineName(context); + if(name != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","runBlocking",name); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","runBlocking"); + } + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Block", block.toString()); + + if (!(block instanceof NRFunction2SuspendWrapper)) { + block = (NRFunction2SuspendWrapper, ?>) new NRFunction2SuspendWrapper(block); + } + return Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public static Deferred async(CoroutineScope scope, CoroutineContext context, CoroutineStart cStart, + Function2, ?> block) { + if (Utils.continueWithScope(scope)) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineStart", cStart.name()); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineScope-Class", scope.getClass().getName()); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineScope-CoroutineName", Utils.getCoroutineName(scope.getCoroutineContext())); + + String name = Utils.getCoroutineName(context); + if(name == null) { + name = Utils.getCoroutineName(scope.getCoroutineContext()); + } + if(name != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","async",name); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineName", name); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","async"); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineName", "Could not determine"); + } + + if(!(block instanceof NRFunction2SuspendWrapper)) { + block = (NRFunction2SuspendWrapper, ?>) new NRFunction2SuspendWrapper(block); + } + } else { + NewRelic.getAgent().getTransaction().ignore(); + } + return Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public static Object invoke(CoroutineDispatcher_Instrumentation dispatcher, + Function2, ?> block, Continuation cont) { + + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Continuation", cont.toString()); + if(!(block instanceof NRFunction2SuspendWrapper)) { + block = (NRFunction2SuspendWrapper, ?>) new NRFunction2SuspendWrapper(block); + } + if(Utils.continueWithContinuation(cont)) { + boolean isSuspend = cont instanceof SuspendFunction; + if(!isSuspend) { + String cont_string = Utils.getContinuationString(cont); + cont = new NRContinuationWrapper<>(cont, cont_string); + } + } + return Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public static kotlinx.coroutines.Job launch(CoroutineScope scope, CoroutineContext context, CoroutineStart cStart, + Function2, ?> block) { + if (Utils.continueWithScope(scope)) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineStart", cStart.name()); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineScope-Class", scope.getClass().getName()); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineScope-CoroutineName", Utils.getCoroutineName(scope.getCoroutineContext())); + + String name = Utils.getCoroutineName(context); + if (name == null) { + name = Utils.getCoroutineName(scope.getCoroutineContext()); + } + if (name != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom", "Builders", "launch", name); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineName", name); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom", "Builders", "launch"); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineName", "Could not determine"); + } + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Block", block.toString()); + if (!(block instanceof NRFunction2SuspendWrapper)) { + block = (NRFunction2SuspendWrapper, ?>) new NRFunction2SuspendWrapper( + block); + } + } else { + NewRelic.getAgent().getTransaction().ignore(); + } + return Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public static Object withContext(CoroutineContext context, Function2, ?> block, + Continuation completion) { + String name = Utils.getCoroutineName(context,completion); + if(name != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","withContext",name); + } else { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Builders","withContext"); + } + if(completion != null) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Completion", completion.toString()); + } + + if(!(block instanceof NRFunction2SuspendWrapper)) { + block = (NRFunction2SuspendWrapper, ?>) new NRFunction2SuspendWrapper(block); + } + if(completion != null && Utils.continueWithContinuation(completion)) { + if(!(completion instanceof NRContinuationWrapper)) { + String cont_string = Utils.getContinuationString(completion); + completion = new NRContinuationWrapper<>(completion, cont_string); + } + } + return Weaver.callOriginal(); + } +} \ No newline at end of file diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CancellableContinuationImpl_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CancellableContinuationImpl_Instrumentation.java new file mode 100644 index 0000000..8dfb3b1 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CancellableContinuationImpl_Instrumentation.java @@ -0,0 +1,64 @@ +package kotlinx.coroutines; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import kotlin.Unit; +import kotlin.coroutines.CoroutineContext; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function3; + +@Weave(originalName = "kotlinx.coroutines.CancellableContinuationImpl") +public abstract class CancellableContinuationImpl_Instrumentation { + + @Trace + public void resumeWith(Object obj) { + Weaver.callOriginal(); + } + + @Trace + public java.lang.Object tryResumeWithException(Throwable t) { + + return Weaver.callOriginal(); + } + + @Trace + public Object tryResume(T t, Object o) { + return Weaver.callOriginal(); + } + + @Trace + public java.lang.Object tryResume(T r, Object obj, Function1 function1) { + return Weaver.callOriginal(); + } + + @Trace + public void completeResume(Object object) { + Weaver.callOriginal(); + } + + @Trace + public boolean cancel(Throwable t) { + return Weaver.callOriginal(); + } + + @Trace + public void invokeOnCancellation(Function1 function1) { + Weaver.callOriginal(); + } + + @Trace + public void resumeUndispatched(CoroutineDispatcher dispatcher, T t) { + Weaver.callOriginal(); + } + + @Trace + public void resumeUndispatchedWithException(CoroutineDispatcher dispatcher, Throwable t) { + Weaver.callOriginal(); + } + + @Trace + public void resume(T r, Function1 function3) { + Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java new file mode 100644 index 0000000..fa7f448 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java @@ -0,0 +1,26 @@ +package kotlinx.coroutines; + +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_14.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.coroutines.CoroutineContext; + +/* + * Dispatchers are used to dispatch tasks to another thread. By wrapping the Runable + * we can track the Coroutine across threads + */ +@Weave(type = MatchType.BaseClass, originalName = "kotlinx.coroutines.CoroutineDispatcher") +public abstract class CoroutineDispatcher_Instrumentation { + + 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-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandlerKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandlerKt_Instrumentation.java new file mode 100644 index 0000000..393f625 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandlerKt_Instrumentation.java @@ -0,0 +1,14 @@ +package kotlinx.coroutines; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(originalName = "kotlinx.coroutines.CoroutineExceptionHandlerKt") +public abstract class CoroutineExceptionHandlerKt_Instrumentation { + + @Trace + public static void handleCoroutineException(kotlin.coroutines.CoroutineContext ctx,Throwable t) { + Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandler_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandler_Instrumentation.java new file mode 100644 index 0000000..9aa2936 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/CoroutineExceptionHandler_Instrumentation.java @@ -0,0 +1,17 @@ +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; + +@Weave(type = MatchType.Interface, originalName = "kotlinx.coroutines.CoroutineExceptionHandler") +public abstract class CoroutineExceptionHandler_Instrumentation { + + @Trace + public void handleException(kotlin.coroutines.CoroutineContext ctx, java.lang.Throwable t) { + NewRelic.noticeError(t); + Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DelayKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DelayKt_Instrumentation.java new file mode 100644 index 0000000..b37dd6f --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DelayKt_Instrumentation.java @@ -0,0 +1,30 @@ +package kotlinx.coroutines; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRDelayContinuation; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; +import kotlin.Unit; +import kotlin.coroutines.Continuation; + +@Weave(originalName = "kotlinx.coroutines.DelayKt") +public class DelayKt_Instrumentation { + + @Trace + public static Object awaitCancellation(Continuation cont) { + if(Utils.DELAYED_ENABLED && !(cont instanceof NRDelayContinuation)) { + cont = new NRDelayContinuation<>(cont,"AwaitCancellation"); + } + return Weaver.callOriginal(); + } + + @Trace + public static Object delay(long timeMillis, Continuation cont) { + if(Utils.DELAYED_ENABLED && !(cont instanceof NRDelayContinuation)) { + cont = new NRDelayContinuation<>(cont,"Delay"); + } + return Weaver.callOriginal(); + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/Delay_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/Delay_Instrumentation.java new file mode 100644 index 0000000..3b57f1e --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/Delay_Instrumentation.java @@ -0,0 +1,37 @@ +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_14.NRDelayCancellableContinuation; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRDelayContinuation; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; +import kotlin.Unit; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; + +@Weave(type = MatchType.Interface, originalName = "kotlinx.coroutines.Delay") +public class Delay_Instrumentation { + + @Trace + public Object delay(long timeMills, Continuation continuation) { + if(Utils.DELAYED_ENABLED && !(continuation instanceof NRDelayContinuation)) { + continuation = new NRDelayContinuation<>(continuation,"Delay"); + } + return Weaver.callOriginal(); + } + + @Trace + public void scheduleResumeAfterDelay(long timeMills, CancellableContinuation continuation) { + if(Utils.DELAYED_ENABLED && !(continuation instanceof NRDelayContinuation)) { + continuation = new NRDelayCancellableContinuation<>(continuation,"scheduleResumeAfterDelay"); + } + Weaver.callOriginal(); + } + + @Trace + public DisposableHandle invokeOnTimeout(long timeMills, Runnable r, CoroutineContext context) { + return Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DispatcherExecutor_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DispatcherExecutor_Instrumentation.java new file mode 100644 index 0000000..6f7cd3c --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/DispatcherExecutor_Instrumentation.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_14.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +@Weave(type = MatchType.BaseClass, originalName = "kotlinx.coroutines.DispatcherExecutor") +abstract class DispatcherExecutor_Instrumentation { + + @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/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/EventLoopImplBase_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/EventLoopImplBase_Instrumentation.java new file mode 100644 index 0000000..19d4237 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/EventLoopImplBase_Instrumentation.java @@ -0,0 +1,80 @@ +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.MatchType; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +@Weave(originalName = "kotlinx.coroutines.EventLoopImplBase") +public abstract class EventLoopImplBase_Instrumentation { + + @Weave(type = MatchType.BaseClass, originalName = "kotlinx.coroutines.EventLoopImplBase$DelayedTask") + public static abstract class DelayedTask_Instrumentation { + + public DelayedTask_Instrumentation(long nanos) { + if(Utils.DELAYED_ENABLED) { + token = NewRelic.getAgent().getTransaction().getToken(); + } + } + + public long nanoTime = Weaver.callOriginal(); + + @NewField + protected Token token = null; + + public void dispose() { + if(token != null) { + token.expire(); + token = null; + } + Weaver.callOriginal(); + } + } + + @Weave(originalName = "kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask") + static class DelayedResumeTask_Instrumentation extends DelayedTask_Instrumentation { + + + public DelayedResumeTask_Instrumentation(long nanos, kotlinx.coroutines.CancellableContinuation cont, CoroutineDispatcher dispatcher) { + super(nanos); + } + + @Trace(async = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + + NewRelic.recordResponseTimeMetric("Custom/Kotlin/DelayedResumeTask", nanoTime); + Weaver.callOriginal(); + } + } + + @Weave(originalName = "kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask") + static class DelayedRunnableTask_Instrumentation extends DelayedTask_Instrumentation { + + public DelayedRunnableTask_Instrumentation(long nanos, Runnable r) { + super(nanos); + } + + @Trace(async = true) + public void run() { + if(token != null) { + token.linkAndExpire(); + token = null; + } + NewRelic.recordResponseTimeMetric("Custom/Kotlin/DelayedResumeTask", nanoTime); + Weaver.callOriginal(); + } + } + + @Weave(originalName = "kotlinx.coroutines.EventLoopImplBase$DelayedTaskQueue") + public static class DelayedTaskQueue_Instrumentation { + + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java new file mode 100644 index 0000000..817a87d --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java @@ -0,0 +1,34 @@ +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(originalName = "kotlinx.coroutines.ResumeUndispatchedRunnable") +abstract class ResumeUndispatchedRunnable_Instrumentation { + + @NewField + private Token token = null; + + public ResumeUndispatchedRunnable_Instrumentation(CoroutineDispatcher_Instrumentation dispatcher, CancellableContinuation cont) { + 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-nativemt/src/main/java/kotlinx/coroutines/YieldKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/YieldKt_Instrumentation.java new file mode 100644 index 0000000..7f57ee5 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/YieldKt_Instrumentation.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.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.Unit; +import kotlin.coroutines.Continuation; + +@Weave(originalName = "kotlinx.coroutines.YieldKt") +public class YieldKt_Instrumentation { + + @Trace + public static Object yield(Continuation cont) { + String cont_string = Utils.getContinuationString(cont); + if(cont_string != null) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","Yield","yield",cont_string); + } + return Weaver.callOriginal(); + } +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/BufferedChannel_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/BufferedChannel_Instrumentation.java new file mode 100644 index 0000000..977c207 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/BufferedChannel_Instrumentation.java @@ -0,0 +1,15 @@ +package kotlinx.coroutines.channels; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +/** + * Included to avoid loading in version 1.7.x + * One difference between 1.5.x and 1.7.x is the class BufferedChannel which was + * introduced in 1.7 + * + * @author dhilpipre + * + */ +@SkipIfPresent(originalName = "kotlinx.coroutines.channels.BufferedChannel") +public class BufferedChannel_Instrumentation { +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/ChannelCoroutine_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/ChannelCoroutine_Instrumentation.java new file mode 100644 index 0000000..51cacb5 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/channels/ChannelCoroutine_Instrumentation.java @@ -0,0 +1,35 @@ +package kotlinx.coroutines.channels; + +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 kotlin.Unit; +import kotlin.coroutines.Continuation; + +@Weave(originalName = "kotlinx.coroutines.channels.ChannelCoroutine") +public abstract class ChannelCoroutine_Instrumentation { + + @Trace(dispatcher = true) + public Object receive(Continuation cont) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","Coroutines","Channel",getClass().getSimpleName(),"receive"); + + return Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public void cancel(java.util.concurrent.CancellationException ex) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","Coroutines","Channel",getClass().getSimpleName(),"Cancel"); + + Weaver.callOriginal(); + } + + @Trace(dispatcher = true) + public Object send(E e, Continuation cont) { + NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","Coroutines","Channel",getClass().getSimpleName(),"send"); + return Weaver.callOriginal(); + } + + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt_Instrumentation.java new file mode 100644 index 0000000..a6b31d4 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt_Instrumentation.java @@ -0,0 +1,92 @@ +package kotlinx.coroutines.intrinsics; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.TracedMethod; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction1SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction1Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction2SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.coroutines.Continuation; +import kotlin.coroutines.jvm.internal.SuspendFunction; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; + +@Weave(originalName = "kotlinx.coroutines.intrinsics.CancellableKt") +public abstract class CancellableKt_Instrumentation { + + @Trace + public static void startCoroutineCancellable(Function1, ?> f, Continuation cont) { + String continuationString = Utils.getContinuationString(cont); + if(!(cont instanceof SuspendFunction)) { + if(!(cont instanceof NRContinuationWrapper) && Utils.continueWithContinuation(cont)) { + cont = new NRContinuationWrapper<>(cont, continuationString); + } + } + if(continuationString != null) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Continuation", continuationString); + } + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Suspend-Type", "Function1"); + if(!(f instanceof NRFunction1SuspendWrapper)) { + f = new NRFunction1SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + @Trace + public static void startCoroutineCancellable(Function2, ?> f, R receiver, Continuation cont, Function1 onCancellation) { + String continuationString = Utils.getContinuationString(cont); + if(!(cont instanceof SuspendFunction)) { + // create continuation wrapper if needed + if(Utils.continueWithContinuation(cont) && !(cont instanceof NRContinuationWrapper)) { + cont = new NRContinuationWrapper<>(cont, continuationString); + } + } + if(continuationString != null) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Continuation", continuationString); + } + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Suspend-Type", "Function2"); + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Receiver", receiver.getClass().getName()); + + if(onCancellation != null && !(onCancellation instanceof NRFunction1Wrapper)) { + onCancellation = new NRFunction1Wrapper<>(onCancellation); + + } + if(!(f instanceof NRFunction2SuspendWrapper)) { + f = new NRFunction2SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + @Trace + public static void startCoroutineCancellable(Continuation completion, Continuation cont) { + String completionString = Utils.getContinuationString(completion); + if(!(completion instanceof SuspendFunction)) { + // create continuation wrapper if needed + if(Utils.continueWithContinuation(completion) && !(completion instanceof NRContinuationWrapper)) { + completion = new NRContinuationWrapper<>(completion, completionString); + } + } + String continuationString = Utils.getContinuationString(cont); + if(!(cont instanceof SuspendFunction)) { + // create continuation wrapper if needed + if(Utils.continueWithContinuation(cont) && !(cont instanceof NRContinuationWrapper)) { + cont = new NRContinuationWrapper<>(cont, continuationString); + } + } + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + if(completionString != null) { + traced.addCustomAttribute("Completion", completionString); + } + if(continuationString != null) { + NewRelic.getAgent().getTracedMethod().addCustomAttribute("Continuation", continuationString); + } + traced.addCustomAttribute("Suspend-Type", "None"); + Weaver.callOriginal(); + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt_Instrumentation.java b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt_Instrumentation.java new file mode 100644 index 0000000..84823a1 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt_Instrumentation.java @@ -0,0 +1,86 @@ +package kotlinx.coroutines.intrinsics; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.TracedMethod; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction1SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.NRFunction2SuspendWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_14.Utils; + +import kotlin.coroutines.Continuation; +import kotlin.coroutines.jvm.internal.SuspendFunction; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; +import kotlinx.coroutines.internal.ScopeCoroutine; + +@Weave(originalName = "kotlinx.coroutines.intrinsics.UndispatchedKt") +public class UndispatchedKt_Instrumentation { + + @Trace + public static void startCoroutineUnintercepted(Function1, ?> f, Continuation cont) { + String continuationString = Utils.getContinuationString(cont); + if(!(cont instanceof SuspendFunction)) { + if(!(cont instanceof NRContinuationWrapper) && Utils.continueWithContinuation(cont)) { + cont = new NRContinuationWrapper<>(cont, continuationString); + } + } + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + traced.addCustomAttribute("Suspend-Type", "Function1"); + if(continuationString != null) { + traced.addCustomAttribute("Continuation", continuationString); + } + if(!(f instanceof NRFunction1SuspendWrapper)) { + f = new NRFunction1SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + @Trace + public static void startCoroutineUndispatched(Function2, ?> f, R receiver, + Continuation cont) { + String continuationString = Utils.getContinuationString(cont); + if(!(cont instanceof SuspendFunction)) { + if(!(cont instanceof NRContinuationWrapper) && Utils.continueWithContinuation(cont)) { + cont = new NRContinuationWrapper<>(cont, continuationString); + } + } + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + traced.addCustomAttribute("Suspend-Type", "Function2"); + if(continuationString != null) { + traced.addCustomAttribute("Continuation", continuationString); + } + traced.addCustomAttribute("Receiver", receiver.getClass().getName()); + if(!(f instanceof NRFunction2SuspendWrapper)) { + f = new NRFunction2SuspendWrapper<>(f); + } + Weaver.callOriginal(); + } + + @Trace + public static Object startUndispatchedOrReturn(ScopeCoroutine scope, R receiver, + Function2, ?> f) { + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + traced.addCustomAttribute("Suspend-Type", "Function2"); + traced.addCustomAttribute("Receiver", receiver.getClass().getName()); + if(!(f instanceof NRFunction2SuspendWrapper)) { + f = new NRFunction2SuspendWrapper<>(f); + } + return Weaver.callOriginal(); + } + + @Trace + public static Object startUndispatchedOrReturnIgnoreTimeout(ScopeCoroutine scope, R receiver, + Function2, ?> f) { + TracedMethod traced = NewRelic.getAgent().getTracedMethod(); + traced.addCustomAttribute("Suspend-Type", "Function2"); + traced.addCustomAttribute("Receiver", receiver.getClass().getName()); + if(!(f instanceof NRFunction2SuspendWrapper)) { + f = new NRFunction2SuspendWrapper<>(f); + } + return Weaver.callOriginal(); + } + +} diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/CoroutineNameUtils.kt b/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/CoroutineNameUtils.kt new file mode 100644 index 0000000..5060d93 --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/CoroutineNameUtils.kt @@ -0,0 +1,6 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14 + +import kotlinx.coroutines.CoroutineName +import kotlin.coroutines.CoroutineContext + +fun CoroutineContext.getCoroutineName(): String? = this[CoroutineName.Key]?.name diff --git a/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/NRTokenContext.kt b/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/NRTokenContext.kt new file mode 100644 index 0000000..e05a2cf --- /dev/null +++ b/Kotlin-Coroutines_1.4-nativemt/src/main/kotlin/com/newrelic/instrumentation/kotlin/coroutines_14/NRTokenContext.kt @@ -0,0 +1,34 @@ +package com.newrelic.instrumentation.kotlin.coroutines_14 + +import kotlin.coroutines.* +import com.newrelic.api.agent.Token +import org.jetbrains.annotations.Nullable + +data class TokenContext(@Nullable var token: Token) + +class TokenContextElement(val context: TokenContext) : AbstractCoroutineContextElement(Key) { + companion object Key : CoroutineContext.Key +} + +fun CoroutineContext.getTokenContext(): TokenContext? = this[TokenContextElement]?.context + +fun CoroutineContext.getTokenContextOrNull(): TokenContext? = this[TokenContextElement]?.context + +fun addTokenContext(context : CoroutineContext, @Nullable token : Token) : CoroutineContext { + val tokenContext = TokenContext(token) + return context + TokenContextElement(tokenContext) +} + +fun removeTokenContext(context : CoroutineContext) : CoroutineContext { + val tokenContext = context.getTokenContextOrNull(); + if (tokenContext != null) { + @Nullable var token = tokenContext.token + token.expire() + + return context.minusKey(TokenContextElement.Key) + } + return context +} + + + diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java index b7b1a9c..a25b182 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java @@ -7,6 +7,8 @@ import kotlin.coroutines.Continuation; import kotlinx.coroutines.DispatchedTask; +import java.util.logging.Level; + public class NRRunnable implements Runnable { private Runnable delegate = null; @@ -14,6 +16,7 @@ public class NRRunnable implements Runnable { private static boolean isTransformed = false; public NRRunnable(Runnable r,Token t) { + NewRelic.getAgent().getLogger().log(Level.FINER, new Exception("Constructing NRRunnable"), "NRRunnable {0}", r.toString()); if(!isTransformed) { AgentBridge.instrumentation.retransformUninstrumentedClass(getClass()); isTransformed = true; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java index b9429e9..c991160 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt_Instrumentation.java @@ -14,6 +14,8 @@ import kotlin.coroutines.jvm.internal.SuspendFunction; import kotlin.jvm.functions.Function2; +import java.util.logging.Level; + @SuppressWarnings({ "unchecked", "rawtypes" }) @Weave(originalName = "kotlinx.coroutines.BuildersKt") public class BuildersKt_Instrumentation { @@ -64,7 +66,7 @@ public static Deferred async(CoroutineScope scope, CoroutineContext conte } @Trace(dispatcher = true) - public static Object invoke(CoroutineDispatcher_Instrumentation dispatcher, + public static Object invoke(CoroutineDispatcher dispatcher, Function2, ?> block, Continuation cont) { NewRelic.getAgent().getTracedMethod().addCustomAttribute("Continuation", cont.toString()); @@ -94,6 +96,7 @@ public static kotlinx.coroutines.Job launch(CoroutineScope scope, CoroutineConte name = Utils.getCoroutineName(scope.getCoroutineContext()); } if (name != null) { + NewRelic.getAgent().getLogger().log(Level.FINE, new Exception("Call to BuildersKt.launch"), "Call to start {0}", name); NewRelic.getAgent().getTracedMethod().setMetricName("Custom", "Builders", "launch", name); NewRelic.getAgent().getTracedMethod().addCustomAttribute("CoroutineName", name); } else { diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java index 55726db..e4c7dcf 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher_Instrumentation.java @@ -7,6 +7,7 @@ import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.internal.LimitedDispatcher; /* * Dispatchers are used to dispatch tasks to another thread. By wrapping the Runable @@ -16,11 +17,15 @@ public abstract class CoroutineDispatcher_Instrumentation { public void dispatch(CoroutineContext ctx, Runnable r) { - NRRunnable wrapper = Utils.getRunnableWrapper(r); - if(wrapper != null) { - r = wrapper; + String classname = getClass().getName(); + + if(!classname.contains("LimitedDispatcher")) { + NRRunnable wrapper = Utils.getRunnableWrapper(r); + if(wrapper != null) { + r = wrapper; + } } - + Weaver.callOriginal(); } } diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java index 817a87d..6c41f12 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/ResumeUndispatchedRunnable_Instrumentation.java @@ -13,7 +13,7 @@ abstract class ResumeUndispatchedRunnable_Instrumentation { @NewField private Token token = null; - public ResumeUndispatchedRunnable_Instrumentation(CoroutineDispatcher_Instrumentation dispatcher, CancellableContinuation cont) { + public ResumeUndispatchedRunnable_Instrumentation(CoroutineDispatcher dispatcher, CancellableContinuation cont) { Token t = NewRelic.getAgent().getTransaction().getToken(); if(t != null && t.isActive()) { token = t; diff --git a/settings.gradle b/settings.gradle index 4e38b63..c96e97b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ include 'Kotlin-Coroutines_1.0' include 'Kotlin-Coroutines_1.1' include 'Kotlin-Coroutines_1.2' include 'Kotlin-Coroutines_1.4' +include 'Kotlin-Coroutines_1.4-nativemt' include 'Kotlin-Coroutines_1.5' include 'Kotlin-Coroutines_1.7' include 'Kotlin-Coroutines_1.9'