diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9a98c..bc6f753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Version: [v1.0.6](https://github.com/newrelic/newrelic-java-kotlin-coroutines/releases/tag/v1.0.6) | Created: 2025-03-17 + + ## Version: [v1.0.5](https://github.com/newrelic/newrelic-java-kotlin-coroutines/releases/tag/v1.0.5) | Created: 2024-10-21 diff --git a/Kotlin-Coroutines-Suspends/build.gradle b/Kotlin-Coroutines-Suspends/build.gradle new file mode 100644 index 0000000..149b584 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/build.gradle @@ -0,0 +1,32 @@ + +// Build.gradle generated for instrumentation module Kotlin-Coroutines-Core + +apply plugin: 'java' + +dependencies { + implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.4.0' + + // New Relic Java Agent dependencies + implementation 'com.newrelic.agent.java:newrelic-agent:6.4.1' + implementation 'com.newrelic.agent.java:newrelic-api:6.4.1' + implementation fileTree(include: ['*.jar'], dir: '../libs') + implementation fileTree(include: ['*.jar'], dir: '../test-lib') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.labs.Kotlin-Coroutines-Suspends' + attributes 'Implementation-Vendor': 'New Relic Labs' + attributes 'Implementation-Vendor-Id': 'com.newrelic.labs' + attributes 'Implementation-Version': 2.0 + attributes 'Agent-Class': 'com.newrelic.instrumentation.kotlin.coroutines.tracing.CoroutinesPreMain' + } +} + +verifyInstrumentation { + // Verifier plugin documentation: + // https://github.com/newrelic/newrelic-gradle-verify-instrumentation + // Example: + // passes 'javax.servlet:servlet-api:[2.2,2.5]' + // exclude 'javax.servlet:servlet-api:2.4.public_draft' +} \ No newline at end of file diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/SuspendIgnores.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/SuspendIgnores.java new file mode 100644 index 0000000..47d3409 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/SuspendIgnores.java @@ -0,0 +1,76 @@ +package com.newrelic.instrumentation.kotlin.coroutines; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; + +public class SuspendIgnores { + + private static final List ignoredSuspends = new ArrayList(); + private static final String SUSPENDSIGNORECONFIG = "Coroutines.ignores.suspends"; + private static final List ignoredPackages = new ArrayList<>(); + + static { + Config config = NewRelic.getAgent().getConfig(); + String value = config.getValue(SUSPENDSIGNORECONFIG); + init(value); + ignoredPackages.add("kotlin.coroutines"); + ignoredPackages.add("kotlinx.coroutines"); + } + + private static void init(String value) { + if(value != null && !value.isEmpty()) { + String[] ignores = value.split(","); + for(String ignore : ignores) { + addIgnore(ignore); + } + } + } + + public static void reset(Config config) { + ignoredSuspends.clear(); + String value = config.getValue(SUSPENDSIGNORECONFIG); + init(value); + } + + public static void addIgnore(String s) { + if(!ignoredSuspends.contains(s)) { + ignoredSuspends.add(s); + NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore suspends named {0}", s); + } + } + + public static boolean ignoreSuspend(Object obj) { + String objString = obj.toString(); + Class clazz = obj.getClass(); + String className = clazz.getName(); + String packageName = clazz.getPackage().getName(); + + for(String ignored : ignoredPackages) { + if(packageName.startsWith(ignored)) { + return true; + } + } + + boolean objStringMatch = ignoredSuspends.contains(objString); + boolean classNameMatch = ignoredSuspends.contains(className); + + if(objStringMatch || classNameMatch) { + return true; + } + + for(String s : ignoredSuspends) { + Pattern pattern = Pattern.compile(s); + Matcher matcher1 = pattern.matcher(objString); + Matcher matcher2 = pattern.matcher(className); + if(matcher1.matches() || matcher2.matches()) return true; + } + + return false; + } +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java new file mode 100644 index 0000000..da25bf9 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/Utils.java @@ -0,0 +1,41 @@ +package com.newrelic.instrumentation.kotlin.coroutines; + +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; + +public class Utils implements AgentConfigListener { + + private static final Utils INSTANCE = new Utils(); + 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"; + public static String sub = "createCoroutineFromSuspendFunction"; + private static final String CONT_LOC = "Continuation at"; + + static { + ServiceFactory.getConfigService().addIAgentConfigListener(INSTANCE); + Config config = NewRelic.getAgent().getConfig(); + SuspendIgnores.reset(config); + } + + @Override + public void configChanged(String appName, AgentConfig agentConfig) { + SuspendIgnores.reset(agentConfig); + } + + public static String getSuspendString(String cont_string, Object obj) { + if(cont_string.equals(CREATEMETHOD1) || cont_string.equals(CREATEMETHOD2)) return sub; + if(cont_string.startsWith(CONT_LOC)) { + return cont_string; + } + + int index = cont_string.indexOf('@'); + if(index > -1) { + return cont_string.substring(0, index); + } + + return obj.getClass().getName(); + } +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/CoroutinesPreMain.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/CoroutinesPreMain.java new file mode 100644 index 0000000..e1dfd9a --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/CoroutinesPreMain.java @@ -0,0 +1,84 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import java.lang.instrument.Instrumentation; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import com.newrelic.agent.TracerService; +import com.newrelic.agent.core.CoreService; +import com.newrelic.agent.instrumentation.ClassTransformerService; +import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory; +import com.newrelic.agent.instrumentation.context.InstrumentationContextManager; +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.api.agent.NewRelic; + +public class CoroutinesPreMain { + + private static int max_retries = 20; + private static ScheduledExecutorService executor = null; + + public static void premain(String args, Instrumentation inst) { + boolean b = setup(); + if(!b) { + executor = Executors.newSingleThreadScheduledExecutor(); + executor.schedule(new Setup(), 100L, TimeUnit.MILLISECONDS); + } + + } + + public static boolean setup() { + + TracerService tracerService = ServiceFactory.getTracerService(); + ClassTransformerService classTransformerService = ServiceFactory.getClassTransformerService(); + CoreService coreService = ServiceFactory.getCoreService(); + + if(tracerService != null && classTransformerService != null && coreService != null) { + tracerService.registerTracerFactory(SuspendTracerFactory.TRACER_FACTORY_NAME, new SuspendTracerFactory()); + InstrumentationContextManager contextMgr = classTransformerService.getContextManager(); + + if(contextMgr != null) { + SuspendClassTransformer suspendTransformer = new SuspendClassTransformer(contextMgr); + SuspendClassAndMethod suspendMatcher = new SuspendClassAndMethod(); + ClassMatchVisitorFactory suspendMatchVistor = suspendTransformer.addMatcher(suspendMatcher); + + Set factories = new HashSet<>(); + factories.add(suspendMatchVistor); + Class[] allLoadedClasses = coreService.getInstrumentation().getAllLoadedClasses(); + + classTransformerService.retransformMatchingClassesImmediately(allLoadedClasses, factories); + + return true; + } + } + + return false; + } + + private static class Setup implements Runnable { + + private static int count = 0; + + @Override + public void run() { + count++; + NewRelic.getAgent().getLogger().log(Level.FINE, "Call {0} to attempt setting up Suspend ClassTransformer",count); + boolean b = setup(); + + if(!b) { + if(count < max_retries) { + executor.schedule(this, 2L, TimeUnit.SECONDS); + } else { + NewRelic.getAgent().getLogger().log(Level.FINE, "Failed to initiate Suspend Client Transformer after {0} tries", max_retries); + executor.shutdownNow(); + } + } + + } + + } + +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassAndMethod.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassAndMethod.java new file mode 100644 index 0000000..bf392fd --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassAndMethod.java @@ -0,0 +1,27 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class SuspendClassAndMethod implements ClassAndMethodMatcher { + + private ClassMatcher classMatcher; + private MethodMatcher methodMatcher; + + public SuspendClassAndMethod() { + classMatcher = new SuspendClassMatcher(); + methodMatcher = new SuspendMethodMatcher(); + } + + @Override + public ClassMatcher getClassMatcher() { + return classMatcher; + } + + @Override + public MethodMatcher getMethodMatcher() { + return methodMatcher; + } + +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassMatcher.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassMatcher.java new file mode 100644 index 0000000..b9093ac --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassMatcher.java @@ -0,0 +1,49 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import java.util.ArrayList; +import java.util.Collection; + +import com.newrelic.agent.deps.org.objectweb.asm.ClassReader; +import com.newrelic.agent.instrumentation.classmatchers.ChildClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.NotMatcher; + +public class SuspendClassMatcher extends ClassMatcher { + + ChildClassMatcher matcher; + NotMatcher nrMatcher; + + public SuspendClassMatcher() { + matcher = new ChildClassMatcher("kotlin.coroutines.jvm.internal.BaseContinuationImpl",false); + nrMatcher = new NotMatcher(new ExactClassMatcher("com.newrelic.instrumentation.kotlin.coroutines.NRWrappedSuspend")); + } + + @Override + public boolean isMatch(ClassLoader loader, ClassReader cr) { + return matcher.isMatch(loader, cr) && nrMatcher.isMatch(loader, cr); + } + + @Override + public boolean isMatch(Class clazz) { + return matcher.isMatch(clazz) && nrMatcher.isMatch(clazz); + } + + @Override + public Collection getClassNames() { + Collection childClasses = matcher.getClassNames(); + Collection nrClasses = nrMatcher.getClassNames(); + if(childClasses == null && nrClasses == null) return null; + + ArrayList list = new ArrayList<>(); + if(childClasses != null) { + list.addAll(childClasses); + } + if(nrClasses != null) { + list.addAll(nrClasses); + } + + return list; + } + +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassTransformer.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassTransformer.java new file mode 100644 index 0000000..ee81be0 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendClassTransformer.java @@ -0,0 +1,65 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.InstrumentationType; +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcher.Match; +import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcherBuilder; +import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory; +import com.newrelic.agent.instrumentation.context.ContextClassTransformer; +import com.newrelic.agent.instrumentation.context.InstrumentationContext; +import com.newrelic.agent.instrumentation.context.InstrumentationContextManager; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.tracing.TraceDetailsBuilder; +import com.newrelic.api.agent.NewRelic; + +public class SuspendClassTransformer implements ContextClassTransformer { + + private Map matchers = null; + private final InstrumentationContextManager contextMgr; + + public SuspendClassTransformer(InstrumentationContextManager mgr) { + contextMgr = mgr; + matchers = new HashMap<>(); + } + + protected ClassMatchVisitorFactory addMatcher(ClassAndMethodMatcher matcher) { + NewRelic.getAgent().getLogger().log(Level.FINE, "Adding matcher {0} to service classtransformer", matcher); + OptimizedClassMatcherBuilder builder = OptimizedClassMatcherBuilder.newBuilder(); + builder.addClassMethodMatcher(matcher); + ClassMatchVisitorFactory matchVistor = builder.build(); + matchers.put(matcher.getClass().getSimpleName(), matchVistor); + contextMgr.addContextClassTransformer(matchVistor, this); + return matchVistor; + } + + protected void removeMatcher(ClassAndMethodMatcher matcher) { + ClassMatchVisitorFactory matchVisitor = matchers.remove(matcher.getClass().getSimpleName()); + if(matchVisitor != null) { + contextMgr.removeMatchVisitor(matchVisitor); + } + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer, InstrumentationContext context, Match match) + throws IllegalClassFormatException { + for(Method method : match.getMethods()) { + for(ClassAndMethodMatcher matcher : match.getClassMatches().keySet()) { + if (matcher.getMethodMatcher().matches(MethodMatcher.UNSPECIFIED_ACCESS, method.getName(), + method.getDescriptor(), match.getMethodAnnotations(method))) { + context.putTraceAnnotation(method, TraceDetailsBuilder.newBuilder().setTracerFactoryName(SuspendTracerFactory.TRACER_FACTORY_NAME).setDispatcher(true).setInstrumentationSourceName("CoroutinesCore").setInstrumentationType(InstrumentationType.TraceAnnotation).build()); + } + + } + } + return null; + } + +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendMethodMatcher.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendMethodMatcher.java new file mode 100644 index 0000000..be48c40 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendMethodMatcher.java @@ -0,0 +1,27 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import java.util.Set; + +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.NameMethodMatcher; + +public class SuspendMethodMatcher implements MethodMatcher { + + NameMethodMatcher matcher = null; + + public SuspendMethodMatcher() { + matcher = new NameMethodMatcher("invokeSuspend"); + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + return matcher.matches(access, name, desc, annotations); + } + + @Override + public Method[] getExactMethods() { + return matcher.getExactMethods(); + } + +} diff --git a/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendTracerFactory.java b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendTracerFactory.java new file mode 100644 index 0000000..570ad90 --- /dev/null +++ b/Kotlin-Coroutines-Suspends/src/main/java/com/newrelic/instrumentation/kotlin/coroutines/tracing/SuspendTracerFactory.java @@ -0,0 +1,25 @@ +package com.newrelic.instrumentation.kotlin.coroutines.tracing; + +import com.newrelic.agent.Transaction; +import com.newrelic.agent.tracers.ClassMethodSignature; +import com.newrelic.agent.tracers.DefaultTracer; +import com.newrelic.agent.tracers.Tracer; +import com.newrelic.agent.tracers.TracerFactory; +import com.newrelic.agent.tracers.metricname.SimpleMetricNameFormat; +import com.newrelic.instrumentation.kotlin.coroutines.SuspendIgnores; +import com.newrelic.instrumentation.kotlin.coroutines.Utils; + +public class SuspendTracerFactory implements TracerFactory { + + protected static final String TRACER_FACTORY_NAME = "SUSPEND_TRACER_FACTORY"; + + @Override + public Tracer getTracer(Transaction transaction, ClassMethodSignature sig, Object object, Object[] args) { + + if(SuspendIgnores.ignoreSuspend(object)) { + return null; + } + return new DefaultTracer(transaction, sig, object, new SimpleMetricNameFormat("Custom/SuspendFunction/"+Utils.getSuspendString(object.toString(), object))); + } + +} diff --git a/Kotlin-Coroutines_1.7/build.gradle b/Kotlin-Coroutines_1.7/build.gradle index 83103c3..cac13f8 100644 --- a/Kotlin-Coroutines_1.7/build.gradle +++ b/Kotlin-Coroutines_1.7/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'java' -// targetCompatibility = JavaVersion.VERSION_1_9 +targetCompatibility = JavaVersion.VERSION_1_9 dependencies { implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.7.3' diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/DispatchedTaskIgnores.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/DispatchedTaskIgnores.java similarity index 95% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/DispatchedTaskIgnores.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/DispatchedTaskIgnores.java index 5d239d3..25547fa 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/DispatchedTaskIgnores.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/DispatchedTaskIgnores.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import java.util.ArrayList; import java.util.List; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRContinuationWrapper.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRContinuationWrapper.java similarity index 96% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRContinuationWrapper.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRContinuationWrapper.java index 5a016fa..9773d3b 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRContinuationWrapper.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRContinuationWrapper.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.api.agent.NewRelic; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRCoroutineToken.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRCoroutineToken.java similarity index 93% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRCoroutineToken.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRCoroutineToken.java index d6a63da..93dcdac 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRCoroutineToken.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRCoroutineToken.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import com.newrelic.api.agent.Token; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction1Wrapper.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction1Wrapper.java similarity index 94% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction1Wrapper.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction1Wrapper.java index 37bc3bc..f3f3038 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction1Wrapper.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction1Wrapper.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import com.newrelic.agent.bridge.AgentBridge; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction2Wrapper.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction2Wrapper.java similarity index 94% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction2Wrapper.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction2Wrapper.java index d81eb8e..fce671a 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRFunction2Wrapper.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRFunction2Wrapper.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import com.newrelic.agent.bridge.AgentBridge; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRRunnable.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java similarity index 96% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRRunnable.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java index 7d9cdb3..233dd62 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/NRRunnable.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/NRRunnable.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.api.agent.NewRelic; diff --git a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/Utils.java b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/Utils.java similarity index 99% rename from Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/Utils.java rename to Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/Utils.java index 94850ff..2029f33 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_17/Utils.java +++ b/Kotlin-Coroutines_1.9/src/main/java/com/newrelic/instrumentation/kotlin/coroutines_19/Utils.java @@ -1,4 +1,4 @@ -package com.newrelic.instrumentation.kotlin.coroutines_17; +package com.newrelic.instrumentation.kotlin.coroutines_19; import java.util.ArrayList; import java.util.List; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlin/coroutines/ContinuationKt.java b/Kotlin-Coroutines_1.9/src/main/java/kotlin/coroutines/ContinuationKt.java index 1cf16ae..16b73ad 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlin/coroutines/ContinuationKt.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlin/coroutines/ContinuationKt.java @@ -3,8 +3,8 @@ 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_17.NRFunction1Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRFunction2Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction1Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction2Wrapper; import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function2; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/AbstractCoroutine.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/AbstractCoroutine.java index a600fe7..54c8e52 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/AbstractCoroutine.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/AbstractCoroutine.java @@ -7,8 +7,8 @@ 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_17.NRFunction2Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction2Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt.java index 9f2ac41..e61df65 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/BuildersKt.java @@ -5,10 +5,10 @@ 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_17.NRContinuationWrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRCoroutineToken; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRFunction2Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRCoroutineToken; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction2Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.Unit; import kotlin.coroutines.Continuation; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java index a28b871..8e0220f 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/CoroutineDispatcher.java @@ -3,8 +3,8 @@ 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_17.NRRunnable; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.coroutines.CoroutineContext; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/DispatcherExecutor.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/DispatcherExecutor.java index 034b646..939567d 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/DispatcherExecutor.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/DispatcherExecutor.java @@ -5,8 +5,8 @@ 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_17.NRRunnable; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRRunnable; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; @Weave(type = MatchType.BaseClass) abstract class DispatcherExecutor { diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/EventLoopImplBase.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/EventLoopImplBase.java index 3baf1dc..2847ecb 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/EventLoopImplBase.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/EventLoopImplBase.java @@ -7,7 +7,7 @@ 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_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; @Weave public abstract class EventLoopImplBase { diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/YieldKt.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/YieldKt.java index 9f35049..4d4900a 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/YieldKt.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/YieldKt.java @@ -4,7 +4,7 @@ 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_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.Unit; import kotlin.coroutines.Continuation; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt.java index b564669..23efac0 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/CancellableKt.java @@ -5,10 +5,10 @@ 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_17.NRContinuationWrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRFunction1Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRFunction2Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction1Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction2Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.coroutines.Continuation; import kotlin.coroutines.jvm.internal.SuspendFunction; diff --git a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt.java b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt.java index af2928c..12977d0 100644 --- a/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt.java +++ b/Kotlin-Coroutines_1.9/src/main/java/kotlinx/coroutines/intrinsics/UndispatchedKt.java @@ -5,9 +5,9 @@ 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_17.NRContinuationWrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.NRFunction2Wrapper; -import com.newrelic.instrumentation.kotlin.coroutines_17.Utils; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRContinuationWrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.NRFunction2Wrapper; +import com.newrelic.instrumentation.kotlin.coroutines_19.Utils; import kotlin.coroutines.Continuation; import kotlin.coroutines.jvm.internal.SuspendFunction; diff --git a/README.md b/README.md index f07ed97..94e4574 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,7 @@ Provides instrumentation for Kotlin Coroutines. In particular it will trace the coroutine from its start, suspend and resume. It does this across multilple threads. # Advisory -The current release is experiencing problems especially in the latest versions of the Java Agent. The problem is with the Kotlin-Coroutines-Suspend.jar which is used to track suspend methods. We are working on fixing the problem but if you are experiencing NullPointerExceptions or Inconsistent tracer errors in the Java Agent log, please remove Kotlin-Coroutines-Suspends.jar from the extensions directory and restart the application. -The current release has been modified and no longer contains Kotlin-Coroutines-Suspends.jar -We apologize for the inconvience. We will advise when we have fixed the problem. A new release will be generated when the problem is fixed. +Due to problems revolving around Suspend Functions in Kotlin Coroutines, a fix was required to the Java Agent to resolve these problems. The fix is available in the 8.19.0 version and later of the Java Agent. The instrumentation for Suspend Functions has been included again but does require that you use version 8.19.0 or later of the Java Agent. If you are unable to upgrade the agent, then remove the Kotlin-Coroutines-Suspends.jar from the extensions directory. ## Supported Versions @@ -69,6 +67,10 @@ The following things are captured as part of the instrumentation Instrumentation of methods with high invocation rates can lead to CPU overhead especially if its average response time is very small (i.e. less than a few milliseconds). Therefore it is possible to configure the agent to ignore certain suspend methods, dispatched tasks and continuation resumeWiths. This configuation is done in the newrelic.yml file. +### Suspend Function Tracking + +The instrumentation will track suspend functions that are outside of those within the Kotlin Coroutine framework. This means that it will track suspend functions that are genrated in your code and in frameworks that use Coroutines. + ### Finding Coroutine Scopes to Ignore This is basically meant for Standalone Coroutines that you don't want to track for some reason such as it is a long running task that doesn't need to be tracked. Lazy Coroutines are a good example. If the agent encounters that scope it will stop tracing that transaction, hence if you disable a scope that is part of another transaction rather than just itself it will also disable that transaction. But the configuration is dynamic so you can remove to restore the transaction. To find the value to use for the Coroutine Scope to ignore go into the transaction trace and select the "Custom/Builders/launch" or "Custom/Builders/async" span. In the Attributes tab find CoroutineScope-Class for the value to use as shown below. image @@ -86,7 +88,10 @@ At minumum consider ignoring anything over 50K. Below each rate is the name of the metric, it has the form Custom/DispatchedTask/..., or Custom/WrappedSuspend/... or Custom/ContinuationWrapper/... depending on the query that was run. Collect a list of the remaining metric name (i.e. the ...). -### Configuring Methods to Ignore +### Configuring Methods to Ignore +The configuration supports using regular expression wildcards to ignore multiple items rather than having to list each one separately. For example, to ignore any suspend function in the class com.mycompany.mypackage.MyClass, use com\.mycompany\.mypackage\.MyClass.* +Note that Java Regular Expressions as regular expressions so consult a cheat sheet for guidance. (e.g. https://quickref.me/regex.html) + To configure methods to ignore, edit the newrelic.yml file in the New Relic Java Agent directory. 1. Find the following lines in newrelic.yml @@ -102,7 +107,7 @@ Note that these setting are dynamic, so typically the agent should pick up chang ### Configuring Scopes to Ignore Similar to configuring the method to ignore above except add a line scopes: to the configuration as shown: -image +image ## Building diff --git a/settings.gradle b/settings.gradle index 354b725..0f268f4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,4 @@ include 'Kotlin-Coroutines_1.5' include 'Kotlin-Coroutines_1.7' include 'Kotlin-Coroutines_1.9' include 'kroto-plus-coroutines' +include 'Kotlin-Coroutines-Suspends'