From 9735f12d3b1ae5f6a9b0de0d2fe6efd962998255 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Feb 2021 16:22:05 -0600 Subject: [PATCH] added RxJava1 Finder --- rxjava1-finder/build.gradle | 30 ++++++ .../CompletableReturnMethodMatcher.java | 40 ++++++++ .../ObservableReturnMethodMatcher.java | 64 +++++++++++++ .../rxjava1/RxJava1ClassMatcher.java | 37 +++++++ .../rxjava1/RxJava1LoaderService.java | 96 +++++++++++++++++++ .../frameworks/rxjava1/RxJava1PointCut.java | 38 ++++++++ .../rxjava1/RxJava1ReturnMethodMatcher.java | 39 ++++++++ .../rxjava1/SingleReturnMethodMatcher.java | 40 ++++++++ .../rxjava2/NRSingleObserver.java | 4 +- 9 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 rxjava1-finder/build.gradle create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/CompletableReturnMethodMatcher.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/ObservableReturnMethodMatcher.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ClassMatcher.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1LoaderService.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1PointCut.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ReturnMethodMatcher.java create mode 100644 rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/SingleReturnMethodMatcher.java diff --git a/rxjava1-finder/build.gradle b/rxjava1-finder/build.gradle new file mode 100644 index 0000000..55fb135 --- /dev/null +++ b/rxjava1-finder/build.gradle @@ -0,0 +1,30 @@ + +// Build.gradle generated for instrumentation module rxjava1-finder + +apply plugin: 'java' + +dependencies { + implementation 'io.reactivex:rxjava:1.1.0' + + // New Relic Java Agent dependencies + implementation 'com.newrelic.agent.java:newrelic-agent:6.0.0' + implementation 'com.newrelic.agent.java:newrelic-api:6.0.0' + implementation fileTree(include: ['*.jar'], dir: '../libs') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.rxjava1-finder' + attributes 'Implementation-Vendor': 'New Relic' + attributes 'Implementation-Vendor-Id': 'com.newrelic' + attributes 'Implementation-Version': 1.0 + } +} + +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/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/CompletableReturnMethodMatcher.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/CompletableReturnMethodMatcher.java new file mode 100644 index 0000000..b8afab8 --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/CompletableReturnMethodMatcher.java @@ -0,0 +1,40 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.Set; + +import com.newrelic.agent.deps.org.objectweb.asm.Type; +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.util.asm.Utils; + +public class CompletableReturnMethodMatcher implements MethodMatcher { + + private static final String rxClassname = "rx.Completable"; + + public CompletableReturnMethodMatcher() { + } + + @Override + public Method[] getExactMethods() { + return null; + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + Type type = Type.getReturnType(desc); + String classname = type.getClassName(); + return isCompletable(classname); + } + + private boolean isCompletable(String className) { + if(Utils.isPrimitiveType(className) || className.endsWith("[]") || className.startsWith("com.newrelic")) { + return false; + } + if(className.equals(rxClassname)) { + return true; + } + + return false; + } + +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/ObservableReturnMethodMatcher.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/ObservableReturnMethodMatcher.java new file mode 100644 index 0000000..76fe4e9 --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/ObservableReturnMethodMatcher.java @@ -0,0 +1,64 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.newrelic.agent.deps.org.objectweb.asm.Type; +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.util.asm.Utils; + +public class ObservableReturnMethodMatcher implements MethodMatcher { + + private static final String rxClassname = "rx.Observable"; + private static final String CachedObservable = "rx.internal.operators.CachedObservable"; + private static final String ScalarSync = "rx.internal.util.ScalarSynchronousObservable"; + private static final List classList; + + static { + classList = new ArrayList(); + classList.add(rxClassname); + classList.add(CachedObservable); + classList.add(ScalarSync); + } + + public ObservableReturnMethodMatcher() { + } + + @Override + public Method[] getExactMethods() { + return null; + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + Type type = Type.getReturnType(desc); + String classname = type.getClassName(); + return isObservable(classname); + } + + private boolean isObservable(String className) { + if(Utils.isPrimitiveType(className) || className.endsWith("[]") || className.startsWith("com.newrelic")) { + return false; + } + if(classList.contains(className)) { + return true; + } + + if(className.startsWith("rx.observables") && className.endsWith("Observable")) { + return true; + } + + if(className.startsWith("rx.observables") && className.endsWith("Subject")) { + return true; + } + + if(className.startsWith("rx.subjects") && className.endsWith("Subject")) { + return true; + } + + return false; + } + +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ClassMatcher.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ClassMatcher.java new file mode 100644 index 0000000..a20ec1d --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ClassMatcher.java @@ -0,0 +1,37 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.Collection; +import java.util.Collections; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.deps.org.objectweb.asm.ClassReader; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; + +public class RxJava1ClassMatcher extends ClassMatcher { + + @Override + public Collection getClassNames() { + return Collections.emptyList(); + } + + @Override + public boolean isMatch(Class var1) { + if(var1.isAnnotation()) return false; + + Package classPackage = var1.getPackage(); + boolean b = !classPackage.getName().startsWith("rx."); + return b; + } + + @Override + public boolean isMatch(ClassLoader loader, ClassReader cr) { + if (loader == null) { + loader = AgentBridge.getAgent().getClass().getClassLoader(); + } + String className = cr.getClassName(); + + boolean b = !className.startsWith("rx/"); + return b; + } + +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1LoaderService.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1LoaderService.java new file mode 100644 index 0000000..efca03d --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1LoaderService.java @@ -0,0 +1,96 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; + +import com.newrelic.agent.instrumentation.ClassTransformerService; +import com.newrelic.agent.instrumentation.PointCutClassTransformer; +import com.newrelic.agent.service.AbstractService; +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.api.agent.NewRelic; + +public class RxJava1LoaderService extends AbstractService { + + private ExecutorService executor = null; + + public RxJava1LoaderService() { + super("RxJava1LoaderService"); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + protected void doStart() throws Exception { + ClassTransformerService classTransformerService = ServiceFactory.getClassTransformerService(); + if(classTransformerService != null) { + PointCutClassTransformer classTransformer = classTransformerService.getClassTransformer(); + if(classTransformer != null) { + RxJava1PointCut rxjava1Pointcut = new RxJava1PointCut(classTransformer); + boolean b = classTransformerService.addTraceMatcher(rxjava1Pointcut, "RxJava1"); + NewRelic.getAgent().getLogger().log(Level.FINE, "Result of adding RxJava1Pointcut is {0}", b); + } else { + NewRelic.getAgent().getLogger().log(Level.FINE, "Could not load matcher because ClassTransformer is null"); + startExecutor(); + + } + } else { + NewRelic.getAgent().getLogger().log(Level.FINE, "Could not load matcher because ClassTransformerService is null"); + startExecutor(); + } + } + + @Override + protected void doStop() throws Exception { + + } + + private boolean addTraceMatcher(ClassTransformerService classTransformerService) { + PointCutClassTransformer classTransformer = classTransformerService.getClassTransformer(); + RxJava1PointCut rxjava1Pointcut = new RxJava1PointCut(classTransformer); + return classTransformerService.addTraceMatcher(rxjava1Pointcut, "RxJava1"); + } + + private void startExecutor() { + executor = Executors.newSingleThreadExecutor(); + RunCheck runCheck = new RunCheck(); + executor.submit(runCheck); + NewRelic.getAgent().getLogger().log(Level.FINE, "Submit RunCheck to executor"); + } + + private void shutdownExecutor() { + executor.shutdown(); + NewRelic.getAgent().getLogger().log(Level.FINE, "ReactorLoaderService executor has shut down"); + } + + + private class RunCheck implements Runnable { + + @Override + public void run() { + boolean done = false; + while(!done) { + ClassTransformerService classTransformerService = ServiceFactory.getClassTransformerService(); + if(classTransformerService != null) { + PointCutClassTransformer classTransformer = classTransformerService.getClassTransformer(); + if(classTransformer != null) { + done = true; + boolean b = addTraceMatcher(classTransformerService); + NewRelic.getAgent().getLogger().log(Level.FINE, "Result of adding ReactorPointcut is {0}", b); + } + } else { + try { + Thread.sleep(5000L); + } catch (InterruptedException e) { + } + } + } + shutdownExecutor(); + } + + } + +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1PointCut.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1PointCut.java new file mode 100644 index 0000000..d8de24a --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1PointCut.java @@ -0,0 +1,38 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import com.newrelic.agent.MetricNames; +import com.newrelic.agent.Transaction; +import com.newrelic.agent.instrumentation.PointCutClassTransformer; +import com.newrelic.agent.instrumentation.PointCutConfiguration; +import com.newrelic.agent.instrumentation.TracerFactoryPointCut; +import com.newrelic.agent.tracers.ClassMethodSignature; +import com.newrelic.agent.tracers.OtherRootTracer; +import com.newrelic.agent.tracers.Tracer; +import com.newrelic.agent.tracers.metricname.ClassMethodMetricNameFormat; + +public class RxJava1PointCut extends TracerFactoryPointCut { + + public RxJava1PointCut(PointCutClassTransformer classTransformer) { + super(new PointCutConfiguration("rxjava1"),new RxJava1ClassMatcher(), new RxJava1ReturnMethodMatcher()); + } + + + + @Override + public boolean isDispatcher() { + return true; + } + + @Override + protected Tracer doGetTracer(Transaction transaction, ClassMethodSignature sig, Object rx, Object[] args) { + return new RxJava1MethodTracer(transaction, sig, rx); + } + + private static class RxJava1MethodTracer extends OtherRootTracer { + + public RxJava1MethodTracer(Transaction transaction, ClassMethodSignature sig, Object rx) { + super(transaction,sig,rx, new ClassMethodMetricNameFormat(sig, rx, MetricNames.OTHER_TRANSACTION+"/RxJava1")); + } + + } +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ReturnMethodMatcher.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ReturnMethodMatcher.java new file mode 100644 index 0000000..8a02c45 --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/RxJava1ReturnMethodMatcher.java @@ -0,0 +1,39 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.newrelic.agent.instrumentation.methodmatchers.ManyMethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class RxJava1ReturnMethodMatcher extends ManyMethodMatcher { + + private static CompletableReturnMethodMatcher completableMatcher = new CompletableReturnMethodMatcher(); + private static ObservableReturnMethodMatcher observableMatcher = new ObservableReturnMethodMatcher(); + private static SingleReturnMethodMatcher singleMatcher = new SingleReturnMethodMatcher(); + + private static List matchers; + + static { + matchers = new ArrayList(); + matchers.add(completableMatcher); + matchers.add(observableMatcher); + matchers.add(singleMatcher); + } + + public RxJava1ReturnMethodMatcher() { + super(matchers); + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + for(MethodMatcher matcher : matchers) { + if(matcher.matches(access, name, desc, annotations)) { + return true; + } + } + return false; + } + +} diff --git a/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/SingleReturnMethodMatcher.java b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/SingleReturnMethodMatcher.java new file mode 100644 index 0000000..359b2d1 --- /dev/null +++ b/rxjava1-finder/src/main/java/com/newrelic/agent/instrumentation/pointcuts/frameworks/rxjava1/SingleReturnMethodMatcher.java @@ -0,0 +1,40 @@ +package com.newrelic.agent.instrumentation.pointcuts.frameworks.rxjava1; + +import java.util.Set; + +import com.newrelic.agent.deps.org.objectweb.asm.Type; +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.util.asm.Utils; + +public class SingleReturnMethodMatcher implements MethodMatcher { + + private static final String rxClassname = "rx.Single"; + + public SingleReturnMethodMatcher() { + } + + @Override + public Method[] getExactMethods() { + return null; + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + Type type = Type.getReturnType(desc); + String classname = type.getClassName(); + return isSingle(classname); + } + + private boolean isSingle(String className) { + if(Utils.isPrimitiveType(className) || className.endsWith("[]") || className.startsWith("com.newrelic")) { + return false; + } + if(className.equals(rxClassname)) { + return true; + } + + return false; + } + +} diff --git a/rxjava2-2.0/src/main/java/com/newrelic/instrumentation/rxjava2/NRSingleObserver.java b/rxjava2-2.0/src/main/java/com/newrelic/instrumentation/rxjava2/NRSingleObserver.java index fea0fee..35cbdbb 100644 --- a/rxjava2-2.0/src/main/java/com/newrelic/instrumentation/rxjava2/NRSingleObserver.java +++ b/rxjava2-2.0/src/main/java/com/newrelic/instrumentation/rxjava2/NRSingleObserver.java @@ -47,7 +47,7 @@ public void onSubscribe(Disposable d) { } @Override - @Trace(async=true/*,excludeFromTransactionTrace=true*/) + @Trace(async=true,excludeFromTransactionTrace=true) public void onSuccess(T t) { if(token != null) { token.linkAndExpire(); @@ -57,7 +57,7 @@ public void onSuccess(T t) { } @Override - @Trace(async=true/*,excludeFromTransactionTrace=true*/) + @Trace(async=true,excludeFromTransactionTrace=true) public void onError(Throwable e) { NewRelic.noticeError(e); if(token != null) {