From 8c479a54a14dd32fefb4127b3ae7cd291ffcdd71 Mon Sep 17 00:00:00 2001
From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com>
Date: Tue, 29 Jun 2021 15:13:07 +0200
Subject: [PATCH 1/8] WIP
---
.../src/test/resources/log4j2-test.xml | 2 +-
.../apm-scala-concurrent-plugin/pom.xml | 9 +-
.../FutureInstrumentation.java | 94 ++++-
...ic.apm.agent.sdk.ElasticApmInstrumentation | 6 +-
.../FutureInstrumentationSpec.scala | 371 ++++++++++--------
5 files changed, 313 insertions(+), 169 deletions(-)
diff --git a/apm-agent-core/src/test/resources/log4j2-test.xml b/apm-agent-core/src/test/resources/log4j2-test.xml
index db122fc04d..e7c91d2e6c 100644
--- a/apm-agent-core/src/test/resources/log4j2-test.xml
+++ b/apm-agent-core/src/test/resources/log4j2-test.xml
@@ -14,6 +14,6 @@
-
+
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
index e3d39617f1..966cf39810 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
@@ -16,6 +16,11 @@
+
+ org.scala-lang
+ scala-library
+ 2.13.4
+
${project.groupId}
apm-java-concurrent-plugin
@@ -36,9 +41,9 @@
net.alchim31.maven
scala-maven-plugin
- 4.3.1
+ 4.4.0
- 2.13.2
+ 2.13.4
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
index 175f9355e5..0ee73ef9e9 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
@@ -19,21 +19,25 @@
package co.elastic.apm.agent.scalaconcurrent;
import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
+import co.elastic.apm.agent.cache.WeakKeySoftValueLoadingCache;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.concurrent.Future;
+import scala.concurrent.Future$;
+import scala.util.Try;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
-import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.returns;
+import static net.bytebuddy.matcher.ElementMatchers.*;
public abstract class FutureInstrumentation extends TracerAwareInstrumentation {
@@ -41,13 +45,47 @@ public abstract class FutureInstrumentation extends TracerAwareInstrumentation {
public static final WeakConcurrentMap
@@ -41,9 +41,9 @@
net.alchim31.maven
scala-maven-plugin
- 4.4.0
+ 4.5.3
- 2.13.4
+ 2.13.6
From af6e231fc771888e48a3374d67ed8c8a27625006 Mon Sep 17 00:00:00 2001
From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com>
Date: Tue, 6 Jul 2021 20:58:37 +0200
Subject: [PATCH 3/8] WIP
---
.../apm-scala-concurrent-plugin/pom.xml | 1 -
.../FutureInstrumentation.java | 269 ++++++++++--------
...ic.apm.agent.sdk.ElasticApmInstrumentation | 9 +-
.../FutureInstrumentationSpec.scala | 6 +-
4 files changed, 157 insertions(+), 128 deletions(-)
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
index 85eff298c3..c79b7b2cd7 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
@@ -25,7 +25,6 @@
${project.groupId}
apm-java-concurrent-plugin
${project.version}
- test
org.scalameta
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
index 0ee73ef9e9..acd6688847 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
@@ -21,6 +21,7 @@
import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
import co.elastic.apm.agent.cache.WeakKeySoftValueLoadingCache;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
+import co.elastic.apm.agent.sdk.advice.AssignTo;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@@ -36,6 +37,8 @@
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.Executor;
+import co.elastic.apm.agent.concurrent.JavaConcurrent;
import static net.bytebuddy.matcher.ElementMatchers.*;
@@ -53,140 +56,166 @@ public Collection getInstrumentationGroupNames() {
return Arrays.asList("scala-future", "experimental");
}
- public static class FutureObjectInstrumentation extends FutureInstrumentation {
+ public static class BatchedExecutionContextInstrumentation extends FutureInstrumentation {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("scala.concurrent.Future$");
+ return hasSuperType(named("scala.concurrent.BatchingExecutor"));
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return isTypeInitializer();
- }
-
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
- public static void onExit() {
- logger.warn("==============");
- logger.warn("Match on Future.unit");
- logger.warn("==============");
-
- final AbstractSpan> context = tracer.getActive();
- if (context != null) {
- logger.warn("==============");
- logger.warn("Match on Future.unit " + context);
- logger.warn("==============");
- }
- // Remove context on the initial Future.unit initialization such that following
- // chaining methods are not linked to this constant 'origin' Future.
- final Try> unitFuture = Future$.MODULE$.unit().value().get();
- logger.warn(promisesToContext.toString());
-// promisesToContext.put(unitFuture, null);
- }
- }
-
- public static class TransformationConstructorInstrumentation extends FutureInstrumentation {
-
- @Override
- public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("scala.concurrent.impl.Promise$Transformation");
- }
-
- @Override
- public ElementMatcher super MethodDescription> getMethodMatcher() {
- return isConstructor();
- }
-
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
- public static void onExit(@Advice.This Object thiz) {
- final AbstractSpan> context = tracer.getActive();
- if (context != null) {
- logger.warn("==============");
- logger.warn("Constructor " + context);
- logger.warn("==============");
- logger.warn(promisesToContext.toString());
- promisesToContext.put(thiz, context);
- // this span might be ended before the Promise$Transformation#run method starts
- // we have to avoid that this span gets recycled, even in the above mentioned case
- context.incrementReferences();
- }
- }
-
- }
-
- public static class TransformationRunInstrumentation extends FutureInstrumentation {
-
- @Override
- public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("scala.concurrent.impl.Promise$Transformation");
- }
-
- @Override
- public ElementMatcher super MethodDescription> getMethodMatcher() {
- return named("run").and(returns(void.class));
+ return named("submitForExecution").and(returns(void.class)).and(takesArguments(Runnable.class));
}
@Nullable
+ @AssignTo.Argument(0)
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
- public static Object onEnter(@Advice.This Object thiz) {
- logger.warn(promisesToContext.toString());
- AbstractSpan> context = promisesToContext.remove(thiz);
- if (context != null) {
- logger.warn("==============");
- logger.warn("Run " + context);
- logger.warn("==============");
- context.activate();
- // decrements the reference we incremented to avoid that the parent context gets recycled before the promise is run
- // because we have activated it, we can be sure it doesn't get recycled until we deactivate in the OnMethodExit advice
- context.decrementReferences();
- }
- return context;
+ public static Runnable onExecute(@Advice.Argument(0) @Nullable Runnable runnable) {
+ return JavaConcurrent.withContext(runnable, tracer);
}
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
- public static void onExit(@Advice.Enter @Nullable Object abstractSpanObj) {
- if (abstractSpanObj instanceof AbstractSpan>) {
- AbstractSpan> context = (AbstractSpan>) abstractSpanObj;
- context.deactivate();
- }
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onExit(@Nullable @Advice.Thrown Throwable thrown,
+ @Advice.Argument(value = 0) @Nullable Runnable runnable) {
+ JavaConcurrent.doFinally(thrown, runnable);
}
}
- public static class TransformationSubmitWithValueInstrumentation extends FutureInstrumentation {
-
- @Override
- public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("scala.concurrent.impl.Promise$Transformation");
- }
-
- @Override
- public ElementMatcher super MethodDescription> getMethodMatcher() {
- return named("submitWithValue").and(returns(void.class));
- }
-
- @Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
- public static Object onEnter(@Advice.This Object thiz) {
- logger.warn(promisesToContext.toString());
- AbstractSpan> context = promisesToContext.remove(thiz);
- if (context != null) {
- logger.warn("==============");
- logger.warn("SubmitWithValue " + context);
- logger.warn("==============");
- context.activate();
- // decrements the reference we incremented to avoid that the parent context gets recycled before the promise is run
- // because we have activated it, we can be sure it doesn't get recycled until we deactivate in the OnMethodExit advice
- context.decrementReferences();
- }
- return context;
- }
-
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
- public static void onExit(@Advice.Enter @Nullable Object abstractSpanObj) {
- if (abstractSpanObj instanceof AbstractSpan>) {
- AbstractSpan> context = (AbstractSpan>) abstractSpanObj;
- context.deactivate();
- }
- }
- }
+// public static class FutureObjectInstrumentation extends FutureInstrumentation {
+//
+// @Override
+// public ElementMatcher super TypeDescription> getTypeMatcher() {
+// return named("scala.concurrent.Future$");
+// }
+//
+// @Override
+// public ElementMatcher super MethodDescription> getMethodMatcher() {
+// return isTypeInitializer();
+// }
+//
+// @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+// public static void onExit() {
+// logger.warn("==============");
+// logger.warn("Match on Future.unit");
+// logger.warn("==============");
+//
+// final AbstractSpan> context = tracer.getActive();
+// if (context != null) {
+// logger.warn("==============");
+// logger.warn("Match on Future.unit " + context);
+// logger.warn("==============");
+// }
+// // Remove context on the initial Future.unit initialization such that following
+// // chaining methods are not linked to this constant 'origin' Future.
+// final Try> unitFuture = Future$.MODULE$.unit().value().get();
+// logger.warn(promisesToContext.toString());
+//// promisesToContext.put(unitFuture, null);
+// }
+// }
+//
+// public static class TransformationConstructorInstrumentation extends FutureInstrumentation {
+//
+// @Override
+// public ElementMatcher super TypeDescription> getTypeMatcher() {
+// return named("scala.concurrent.impl.Promise$Transformation");
+// }
+//
+// @Override
+// public ElementMatcher super MethodDescription> getMethodMatcher() {
+// return isConstructor();
+// }
+//
+// @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+// public static void onExit(@Advice.This Object thiz) {
+// final AbstractSpan> context = tracer.getActive();
+// if (context != null) {
+// logger.warn("==============");
+// logger.warn("Constructor " + context);
+// logger.warn("==============");
+// logger.warn(promisesToContext.toString());
+// promisesToContext.put(thiz, context);
+// // this span might be ended before the Promise$Transformation#run method starts
+// // we have to avoid that this span gets recycled, even in the above mentioned case
+// context.incrementReferences();
+// }
+// }
+//
+// }
+//
+// public static class TransformationRunInstrumentation extends FutureInstrumentation {
+//
+// @Override
+// public ElementMatcher super TypeDescription> getTypeMatcher() {
+// return named("scala.concurrent.impl.Promise$Transformation");
+// }
+//
+// @Override
+// public ElementMatcher super MethodDescription> getMethodMatcher() {
+// return named("run").and(returns(void.class));
+// }
+//
+// @Nullable
+// @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+// public static Object onEnter(@Advice.This Object thiz) {
+// logger.warn(promisesToContext.toString());
+// AbstractSpan> context = promisesToContext.remove(thiz);
+// if (context != null) {
+// logger.warn("==============");
+// logger.warn("Run " + context);
+// logger.warn("==============");
+// context.activate();
+// // decrements the reference we incremented to avoid that the parent context gets recycled before the promise is run
+// // because we have activated it, we can be sure it doesn't get recycled until we deactivate in the OnMethodExit advice
+// context.decrementReferences();
+// }
+// return context;
+// }
+//
+// @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+// public static void onExit(@Advice.Enter @Nullable Object abstractSpanObj) {
+// if (abstractSpanObj instanceof AbstractSpan>) {
+// AbstractSpan> context = (AbstractSpan>) abstractSpanObj;
+// context.deactivate();
+// }
+// }
+// }
+//
+// public static class TransformationSubmitWithValueInstrumentation extends FutureInstrumentation {
+//
+// @Override
+// public ElementMatcher super TypeDescription> getTypeMatcher() {
+// return named("scala.concurrent.impl.Promise$Transformation");
+// }
+//
+// @Override
+// public ElementMatcher super MethodDescription> getMethodMatcher() {
+// return named("submitWithValue").and(returns(void.class));
+// }
+//
+// @Nullable
+// @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+// public static Object onEnter(@Advice.This Object thiz) {
+// logger.warn(promisesToContext.toString());
+// AbstractSpan> context = promisesToContext.remove(thiz);
+// if (context != null) {
+// logger.warn("==============");
+// logger.warn("SubmitWithValue " + context);
+// logger.warn("==============");
+// context.activate();
+// // decrements the reference we incremented to avoid that the parent context gets recycled before the promise is run
+// // because we have activated it, we can be sure it doesn't get recycled until we deactivate in the OnMethodExit advice
+// context.decrementReferences();
+// }
+// return context;
+// }
+//
+// @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+// public static void onExit(@Advice.Enter @Nullable Object abstractSpanObj) {
+// if (abstractSpanObj instanceof AbstractSpan>) {
+// AbstractSpan> context = (AbstractSpan>) abstractSpanObj;
+// context.deactivate();
+// }
+// }
+// }
}
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
index c66929343c..62e615d092 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation
@@ -1,4 +1,5 @@
-co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$FutureObjectInstrumentation
-co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationConstructorInstrumentation
-co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationRunInstrumentation
-co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationSubmitWithValueInstrumentation
+#co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$FutureObjectInstrumentation
+#co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationConstructorInstrumentation
+#co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationRunInstrumentation
+#co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$TransformationSubmitWithValueInstrumentation
+co.elastic.apm.agent.scalaconcurrent.FutureInstrumentation$BatchedExecutionContextInstrumentation
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/src/test/scala/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentationSpec.scala b/apm-agent-plugins/apm-scala-concurrent-plugin/src/test/scala/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentationSpec.scala
index 8a18b61221..a279245819 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/src/test/scala/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentationSpec.scala
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/src/test/scala/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentationSpec.scala
@@ -197,16 +197,16 @@ class FutureInstrumentationSpec extends FunSuite {
// }
test("Scala Future should not propagate the tracing-context to unrelated threads") {
- implicit val executionContext: ExecutionContext = ExecutionContext.fromExecutorService(new ForkJoinPool(10))
+ implicit val executionContext: ExecutionContext = ExecutionContext.fromExecutorService(new ForkJoinPool(5))
- val fs = (1 to 1).map(transactionNumber => Future {
+ val fs = (1 to 10).map(transactionNumber => Future {
Thread.sleep(10)
val transaction = tracer.startRootTransaction(getClass.getClassLoader).withName(transactionNumber.toString).activate()
println(s"thread=${Thread.currentThread().getId} transaction=$transactionNumber, trace=${tracer.currentTransaction()} starting transaction")
- val futures = (1 to 1)
+ val futures = (1 to 10)
.map(futureNumber => Future {
Thread.sleep(10)
From 031ec2a31cbc08944115176380d3eba34caaf4c2 Mon Sep 17 00:00:00 2001
From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com>
Date: Sun, 11 Jul 2021 20:37:22 +0200
Subject: [PATCH 4/8] WIP
---
.../latest/testapp/generated/HelloGrpc.java | 2 +-
.../FutureInstrumentation.java | 210 ++++-----
...ic.apm.agent.sdk.ElasticApmInstrumentation | 2 +
.../FutureInstrumentationSpec.scala | 434 +++++++++---------
4 files changed, 325 insertions(+), 323 deletions(-)
diff --git a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
index fe0787ef17..8c3814ec99 100644
--- a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
+++ b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
@@ -23,7 +23,7 @@
/**
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.38.1)",
+ value = "by gRPC proto compiler (version 1.39.0)",
comments = "Source: rpc.proto")
public final class HelloGrpc {
diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
index acd6688847..9da85b1ce1 100644
--- a/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
+++ b/apm-agent-plugins/apm-scala-concurrent-plugin/src/main/java/co/elastic/apm/agent/scalaconcurrent/FutureInstrumentation.java
@@ -48,7 +48,7 @@ public abstract class FutureInstrumentation extends TracerAwareInstrumentation {
public static final WeakConcurrentMap