Skip to content

Commit f168d29

Browse files
authored
Merge pull request #39 from newrelic/wildcard-ignores
Wildcard ignores
2 parents c47820c + 666cf36 commit f168d29

29 files changed

+469
-34
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## Version: [v1.0.6](https://github.com/newrelic/newrelic-java-kotlin-coroutines/releases/tag/v1.0.6) | Created: 2025-03-17
2+
3+
14
## Version: [v1.0.5](https://github.com/newrelic/newrelic-java-kotlin-coroutines/releases/tag/v1.0.5) | Created: 2024-10-21
25

36

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
// Build.gradle generated for instrumentation module Kotlin-Coroutines-Core
3+
4+
apply plugin: 'java'
5+
6+
dependencies {
7+
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.4.0'
8+
9+
// New Relic Java Agent dependencies
10+
implementation 'com.newrelic.agent.java:newrelic-agent:6.4.1'
11+
implementation 'com.newrelic.agent.java:newrelic-api:6.4.1'
12+
implementation fileTree(include: ['*.jar'], dir: '../libs')
13+
implementation fileTree(include: ['*.jar'], dir: '../test-lib')
14+
}
15+
16+
jar {
17+
manifest {
18+
attributes 'Implementation-Title': 'com.newrelic.instrumentation.labs.Kotlin-Coroutines-Suspends'
19+
attributes 'Implementation-Vendor': 'New Relic Labs'
20+
attributes 'Implementation-Vendor-Id': 'com.newrelic.labs'
21+
attributes 'Implementation-Version': 2.0
22+
attributes 'Agent-Class': 'com.newrelic.instrumentation.kotlin.coroutines.tracing.CoroutinesPreMain'
23+
}
24+
}
25+
26+
verifyInstrumentation {
27+
// Verifier plugin documentation:
28+
// https://github.com/newrelic/newrelic-gradle-verify-instrumentation
29+
// Example:
30+
// passes 'javax.servlet:servlet-api:[2.2,2.5]'
31+
// exclude 'javax.servlet:servlet-api:2.4.public_draft'
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.logging.Level;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
8+
9+
import com.newrelic.api.agent.Config;
10+
import com.newrelic.api.agent.NewRelic;
11+
12+
public class SuspendIgnores {
13+
14+
private static final List<String> ignoredSuspends = new ArrayList<String>();
15+
private static final String SUSPENDSIGNORECONFIG = "Coroutines.ignores.suspends";
16+
private static final List<String> ignoredPackages = new ArrayList<>();
17+
18+
static {
19+
Config config = NewRelic.getAgent().getConfig();
20+
String value = config.getValue(SUSPENDSIGNORECONFIG);
21+
init(value);
22+
ignoredPackages.add("kotlin.coroutines");
23+
ignoredPackages.add("kotlinx.coroutines");
24+
}
25+
26+
private static void init(String value) {
27+
if(value != null && !value.isEmpty()) {
28+
String[] ignores = value.split(",");
29+
for(String ignore : ignores) {
30+
addIgnore(ignore);
31+
}
32+
}
33+
}
34+
35+
public static void reset(Config config) {
36+
ignoredSuspends.clear();
37+
String value = config.getValue(SUSPENDSIGNORECONFIG);
38+
init(value);
39+
}
40+
41+
public static void addIgnore(String s) {
42+
if(!ignoredSuspends.contains(s)) {
43+
ignoredSuspends.add(s);
44+
NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore suspends named {0}", s);
45+
}
46+
}
47+
48+
public static boolean ignoreSuspend(Object obj) {
49+
String objString = obj.toString();
50+
Class<?> clazz = obj.getClass();
51+
String className = clazz.getName();
52+
String packageName = clazz.getPackage().getName();
53+
54+
for(String ignored : ignoredPackages) {
55+
if(packageName.startsWith(ignored)) {
56+
return true;
57+
}
58+
}
59+
60+
boolean objStringMatch = ignoredSuspends.contains(objString);
61+
boolean classNameMatch = ignoredSuspends.contains(className);
62+
63+
if(objStringMatch || classNameMatch) {
64+
return true;
65+
}
66+
67+
for(String s : ignoredSuspends) {
68+
Pattern pattern = Pattern.compile(s);
69+
Matcher matcher1 = pattern.matcher(objString);
70+
Matcher matcher2 = pattern.matcher(className);
71+
if(matcher1.matches() || matcher2.matches()) return true;
72+
}
73+
74+
return false;
75+
}
76+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines;
2+
3+
import com.newrelic.agent.config.AgentConfig;
4+
import com.newrelic.agent.config.AgentConfigListener;
5+
import com.newrelic.agent.service.ServiceFactory;
6+
import com.newrelic.api.agent.Config;
7+
import com.newrelic.api.agent.NewRelic;
8+
9+
public class Utils implements AgentConfigListener {
10+
11+
private static final Utils INSTANCE = new Utils();
12+
public static final String CREATEMETHOD1 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4";
13+
public static final String CREATEMETHOD2 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3";
14+
public static String sub = "createCoroutineFromSuspendFunction";
15+
private static final String CONT_LOC = "Continuation at";
16+
17+
static {
18+
ServiceFactory.getConfigService().addIAgentConfigListener(INSTANCE);
19+
Config config = NewRelic.getAgent().getConfig();
20+
SuspendIgnores.reset(config);
21+
}
22+
23+
@Override
24+
public void configChanged(String appName, AgentConfig agentConfig) {
25+
SuspendIgnores.reset(agentConfig);
26+
}
27+
28+
public static String getSuspendString(String cont_string, Object obj) {
29+
if(cont_string.equals(CREATEMETHOD1) || cont_string.equals(CREATEMETHOD2)) return sub;
30+
if(cont_string.startsWith(CONT_LOC)) {
31+
return cont_string;
32+
}
33+
34+
int index = cont_string.indexOf('@');
35+
if(index > -1) {
36+
return cont_string.substring(0, index);
37+
}
38+
39+
return obj.getClass().getName();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines.tracing;
2+
3+
import java.lang.instrument.Instrumentation;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
import java.util.concurrent.Executors;
7+
import java.util.concurrent.ScheduledExecutorService;
8+
import java.util.concurrent.TimeUnit;
9+
import java.util.logging.Level;
10+
11+
import com.newrelic.agent.TracerService;
12+
import com.newrelic.agent.core.CoreService;
13+
import com.newrelic.agent.instrumentation.ClassTransformerService;
14+
import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory;
15+
import com.newrelic.agent.instrumentation.context.InstrumentationContextManager;
16+
import com.newrelic.agent.service.ServiceFactory;
17+
import com.newrelic.api.agent.NewRelic;
18+
19+
public class CoroutinesPreMain {
20+
21+
private static int max_retries = 20;
22+
private static ScheduledExecutorService executor = null;
23+
24+
public static void premain(String args, Instrumentation inst) {
25+
boolean b = setup();
26+
if(!b) {
27+
executor = Executors.newSingleThreadScheduledExecutor();
28+
executor.schedule(new Setup(), 100L, TimeUnit.MILLISECONDS);
29+
}
30+
31+
}
32+
33+
public static boolean setup() {
34+
35+
TracerService tracerService = ServiceFactory.getTracerService();
36+
ClassTransformerService classTransformerService = ServiceFactory.getClassTransformerService();
37+
CoreService coreService = ServiceFactory.getCoreService();
38+
39+
if(tracerService != null && classTransformerService != null && coreService != null) {
40+
tracerService.registerTracerFactory(SuspendTracerFactory.TRACER_FACTORY_NAME, new SuspendTracerFactory());
41+
InstrumentationContextManager contextMgr = classTransformerService.getContextManager();
42+
43+
if(contextMgr != null) {
44+
SuspendClassTransformer suspendTransformer = new SuspendClassTransformer(contextMgr);
45+
SuspendClassAndMethod suspendMatcher = new SuspendClassAndMethod();
46+
ClassMatchVisitorFactory suspendMatchVistor = suspendTransformer.addMatcher(suspendMatcher);
47+
48+
Set<ClassMatchVisitorFactory> factories = new HashSet<>();
49+
factories.add(suspendMatchVistor);
50+
Class<?>[] allLoadedClasses = coreService.getInstrumentation().getAllLoadedClasses();
51+
52+
classTransformerService.retransformMatchingClassesImmediately(allLoadedClasses, factories);
53+
54+
return true;
55+
}
56+
}
57+
58+
return false;
59+
}
60+
61+
private static class Setup implements Runnable {
62+
63+
private static int count = 0;
64+
65+
@Override
66+
public void run() {
67+
count++;
68+
NewRelic.getAgent().getLogger().log(Level.FINE, "Call {0} to attempt setting up Suspend ClassTransformer",count);
69+
boolean b = setup();
70+
71+
if(!b) {
72+
if(count < max_retries) {
73+
executor.schedule(this, 2L, TimeUnit.SECONDS);
74+
} else {
75+
NewRelic.getAgent().getLogger().log(Level.FINE, "Failed to initiate Suspend Client Transformer after {0} tries", max_retries);
76+
executor.shutdownNow();
77+
}
78+
}
79+
80+
}
81+
82+
}
83+
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines.tracing;
2+
3+
import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher;
4+
import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher;
5+
import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher;
6+
7+
public class SuspendClassAndMethod implements ClassAndMethodMatcher {
8+
9+
private ClassMatcher classMatcher;
10+
private MethodMatcher methodMatcher;
11+
12+
public SuspendClassAndMethod() {
13+
classMatcher = new SuspendClassMatcher();
14+
methodMatcher = new SuspendMethodMatcher();
15+
}
16+
17+
@Override
18+
public ClassMatcher getClassMatcher() {
19+
return classMatcher;
20+
}
21+
22+
@Override
23+
public MethodMatcher getMethodMatcher() {
24+
return methodMatcher;
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines.tracing;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
6+
import com.newrelic.agent.deps.org.objectweb.asm.ClassReader;
7+
import com.newrelic.agent.instrumentation.classmatchers.ChildClassMatcher;
8+
import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher;
9+
import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher;
10+
import com.newrelic.agent.instrumentation.classmatchers.NotMatcher;
11+
12+
public class SuspendClassMatcher extends ClassMatcher {
13+
14+
ChildClassMatcher matcher;
15+
NotMatcher nrMatcher;
16+
17+
public SuspendClassMatcher() {
18+
matcher = new ChildClassMatcher("kotlin.coroutines.jvm.internal.BaseContinuationImpl",false);
19+
nrMatcher = new NotMatcher(new ExactClassMatcher("com.newrelic.instrumentation.kotlin.coroutines.NRWrappedSuspend"));
20+
}
21+
22+
@Override
23+
public boolean isMatch(ClassLoader loader, ClassReader cr) {
24+
return matcher.isMatch(loader, cr) && nrMatcher.isMatch(loader, cr);
25+
}
26+
27+
@Override
28+
public boolean isMatch(Class<?> clazz) {
29+
return matcher.isMatch(clazz) && nrMatcher.isMatch(clazz);
30+
}
31+
32+
@Override
33+
public Collection<String> getClassNames() {
34+
Collection<String> childClasses = matcher.getClassNames();
35+
Collection<String> nrClasses = nrMatcher.getClassNames();
36+
if(childClasses == null && nrClasses == null) return null;
37+
38+
ArrayList<String> list = new ArrayList<>();
39+
if(childClasses != null) {
40+
list.addAll(childClasses);
41+
}
42+
if(nrClasses != null) {
43+
list.addAll(nrClasses);
44+
}
45+
46+
return list;
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.newrelic.instrumentation.kotlin.coroutines.tracing;
2+
3+
import java.lang.instrument.IllegalClassFormatException;
4+
import java.security.ProtectionDomain;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import java.util.logging.Level;
8+
9+
import com.newrelic.agent.deps.org.objectweb.asm.commons.Method;
10+
import com.newrelic.agent.instrumentation.InstrumentationType;
11+
import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher;
12+
import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcher.Match;
13+
import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcherBuilder;
14+
import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory;
15+
import com.newrelic.agent.instrumentation.context.ContextClassTransformer;
16+
import com.newrelic.agent.instrumentation.context.InstrumentationContext;
17+
import com.newrelic.agent.instrumentation.context.InstrumentationContextManager;
18+
import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher;
19+
import com.newrelic.agent.instrumentation.tracing.TraceDetailsBuilder;
20+
import com.newrelic.api.agent.NewRelic;
21+
22+
public class SuspendClassTransformer implements ContextClassTransformer {
23+
24+
private Map<String, ClassMatchVisitorFactory> matchers = null;
25+
private final InstrumentationContextManager contextMgr;
26+
27+
public SuspendClassTransformer(InstrumentationContextManager mgr) {
28+
contextMgr = mgr;
29+
matchers = new HashMap<>();
30+
}
31+
32+
protected ClassMatchVisitorFactory addMatcher(ClassAndMethodMatcher matcher) {
33+
NewRelic.getAgent().getLogger().log(Level.FINE, "Adding matcher {0} to service classtransformer", matcher);
34+
OptimizedClassMatcherBuilder builder = OptimizedClassMatcherBuilder.newBuilder();
35+
builder.addClassMethodMatcher(matcher);
36+
ClassMatchVisitorFactory matchVistor = builder.build();
37+
matchers.put(matcher.getClass().getSimpleName(), matchVistor);
38+
contextMgr.addContextClassTransformer(matchVistor, this);
39+
return matchVistor;
40+
}
41+
42+
protected void removeMatcher(ClassAndMethodMatcher matcher) {
43+
ClassMatchVisitorFactory matchVisitor = matchers.remove(matcher.getClass().getSimpleName());
44+
if(matchVisitor != null) {
45+
contextMgr.removeMatchVisitor(matchVisitor);
46+
}
47+
}
48+
49+
@Override
50+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
51+
ProtectionDomain protectionDomain, byte[] classfileBuffer, InstrumentationContext context, Match match)
52+
throws IllegalClassFormatException {
53+
for(Method method : match.getMethods()) {
54+
for(ClassAndMethodMatcher matcher : match.getClassMatches().keySet()) {
55+
if (matcher.getMethodMatcher().matches(MethodMatcher.UNSPECIFIED_ACCESS, method.getName(),
56+
method.getDescriptor(), match.getMethodAnnotations(method))) {
57+
context.putTraceAnnotation(method, TraceDetailsBuilder.newBuilder().setTracerFactoryName(SuspendTracerFactory.TRACER_FACTORY_NAME).setDispatcher(true).setInstrumentationSourceName("CoroutinesCore").setInstrumentationType(InstrumentationType.TraceAnnotation).build());
58+
}
59+
60+
}
61+
}
62+
return null;
63+
}
64+
65+
}

0 commit comments

Comments
 (0)