Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
404f6fb
WIP
Feb 16, 2020
9ce7f3f
Add initial scala instrumentation for Future
milanvdm Feb 22, 2020
e0a4335
Remove test-dependency
milanvdm Feb 24, 2020
e57e2d8
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm Mar 29, 2020
3ef57c0
Add first test approach
milanvdm Mar 29, 2020
2dd336d
Use Java for method matching
milanvdm Apr 9, 2020
167ecde
Add dummy test using Scala
milanvdm Apr 14, 2020
90d0b6f
Add java-version
milanvdm Apr 14, 2020
1b5b369
Add first matching instrumentation test
milanvdm Apr 14, 2020
f20a929
WIP
milanvdm Apr 18, 2020
b2d2bb7
Use MUnit
milanvdm Apr 20, 2020
2feb98f
Cleanup pom
milanvdm Apr 22, 2020
0af62ce
Add Future instrumentation
milanvdm Apr 22, 2020
f97b0b1
Add tests
milanvdm Apr 22, 2020
649207b
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm Apr 23, 2020
324ce67
Add plugin to release process
milanvdm Apr 23, 2020
9d0eb39
Instrument ExecutionContext
milanvdm Apr 24, 2020
7ab2834
Merge branch 'master' into add-scala-executor-apm
milanvdm May 8, 2020
ee6da7d
Add onStream instrumentation on AsyncHandler
milanvdm May 8, 2020
ec2b7eb
Add StreamHandler tests
milanvdm May 8, 2020
d9e0762
Merge branch 'keep-spans-active-on-stream-async-http' into add-scala-…
milanvdm May 8, 2020
84417b0
Add Promise instrumentation
milanvdm May 13, 2020
199e514
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm May 13, 2020
ad42993
Cleanup
milanvdm May 15, 2020
49bca5f
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm May 15, 2020
e41f63c
Fix versions
milanvdm May 15, 2020
a0375b7
Make sure spans are not recycled
milanvdm May 16, 2020
accd469
Cleanup Java 9 compilers
milanvdm May 19, 2020
7843966
Add docs
milanvdm May 19, 2020
0f7b257
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm May 19, 2020
f9b8e56
Fix merge
milanvdm May 19, 2020
063ed39
Remove Java version
milanvdm May 27, 2020
6c638d9
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm May 27, 2020
40033fa
Improve testing for edge cases
milanvdm May 29, 2020
5ee2b3b
Simplify failing test
milanvdm May 29, 2020
1721794
Add extra failing test on Future.sequence
milanvdm May 29, 2020
737bb0e
Merge branch 'master' into add-scala-executor-apm
felixbarny Jun 15, 2020
1885c8e
Add java apm dependency to test
milanvdm Jun 15, 2020
c893ac0
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm Jun 17, 2020
f844049
Bump to new snapshot version
milanvdm Jun 17, 2020
0f530a0
Start tracer
milanvdm Jun 23, 2020
c6a6581
Merge remote-tracking branch 'upstream/master' into add-scala-executo…
milanvdm Jun 23, 2020
b68d3d2
Merge branch 'master' into add-scala-executor-apm
felixbarny Jun 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9
8 changes: 8 additions & 0 deletions apm-agent-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
Comment thread
milanvdm marked this conversation as resolved.
Outdated
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
58 changes: 58 additions & 0 deletions apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>apm-agent-plugins</artifactId>
<groupId>co.elastic.apm</groupId>
<version>1.15.1-SNAPSHOT</version>
</parent>

<artifactId>apm-scala-concurrent-plugin</artifactId>
<name>${project.groupId}:${project.artifactId}</name>

<properties>
<apm-agent-parent.base.dir>${project.basedir}/../..</apm-agent-parent.base.dir>
<maven.compiler.source>1.9</maven.compiler.source>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check since Scala does not support all Java version.
Ill also test Scala 2.12 in more detail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested Scala 2.12 and it doesn't work with that version. Not sure how to prevent a plugin to run with a specific version though.

<maven.compiler.target>1.9</maven.compiler.target>
<maven.compiler.testTarget>1.9</maven.compiler.testTarget>
</properties>

<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>org.scalameta</groupId>
<artifactId>munit_2.13</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
Comment thread
felixbarny marked this conversation as resolved.
</dependencies>

<build>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.3.1</version>
<configuration>
<scalaVersion>2.13.1</scalaVersion>
</configuration>
<executions>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Comment thread
milanvdm marked this conversation as resolved.
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 - 2020 Elastic and contributors
* %%
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* #L%
*/
package co.elastic.apm.agent.scala.concurrent;

import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.impl.transaction.TraceContextHolder;
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 scala.concurrent.Promise;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.*;

public abstract class FutureInstrumentation extends ElasticApmInstrumentation {

@VisibleForAdvice
@SuppressWarnings("WeakerAccess")
public static final WeakConcurrentMap<Promise<?>, TraceContextHolder<?>> promisesToContext =
new WeakConcurrentMap.WithInlinedExpunction<>();

@Nonnull
@Override
public Collection<String> getInstrumentationGroupNames() {
return Arrays.asList("concurrent", "future");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking as experimental disables this instrumentation by default.

Suggested change
return Arrays.asList("concurrent", "future");
return Arrays.asList("scala-future", "experimental");

}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return hasSuperType(named("scala.concurrent.impl.Promise$Transformation"));
}

public static class ConstructorInstrumentation extends FutureInstrumentation {

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return isConstructor();
Comment thread
felixbarny marked this conversation as resolved.
}

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.This Promise<?> thiz) {
final TraceContextHolder<?> active = getActive();
if (active != null) {
promisesToContext.put(thiz, active);
}
}

}

public static class RunInstrumentation extends FutureInstrumentation {

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("run").and(returns(void.class));
}

@VisibleForAdvice
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This Promise<?> thiz) {
Comment thread
milanvdm marked this conversation as resolved.
Outdated
final TraceContextHolder<?> context = promisesToContext.getIfPresent(thiz);
if (tracer != null && context != null) {
tracer.activate(context);
Comment thread
milanvdm marked this conversation as resolved.
}
}

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.This Promise<?> thiz) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catch thrown exceptions @Thrown @Nullable Throwable t and captureException(t)

Comment thread
milanvdm marked this conversation as resolved.
Outdated
promisesToContext.remove(thiz);
Comment thread
milanvdm marked this conversation as resolved.
Outdated
Comment thread
milanvdm marked this conversation as resolved.
Outdated
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
co.elastic.apm.agent.scala.concurrent.FutureInstrumentation$ConstructorInstrumentation
co.elastic.apm.agent.scala.concurrent.FutureInstrumentation$RunInstrumentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package co.elastic.apm.agent.scala.concurrent

import java.util.concurrent.Executors

import co.elastic.apm.agent.MockReporter
import co.elastic.apm.agent.bci.ElasticApmAgent
import co.elastic.apm.agent.configuration.{CoreConfiguration, SpyConfiguration}
import co.elastic.apm.agent.impl.transaction.Transaction
import co.elastic.apm.agent.impl.{ElasticApmTracer, ElasticApmTracerBuilder}
import munit.FunSuite
import net.bytebuddy.agent.ByteBuddyAgent
import org.stagemonitor.configuration.ConfigurationRegistry

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor, Future}

class FutureInstrumentationSpec extends FunSuite {

private var reporter: MockReporter = _
private var tracer: ElasticApmTracer = _
private var coreConfiguration: CoreConfiguration = _
private var transaction: Transaction = _

override def beforeEach(context: BeforeEach): Unit = {
reporter = new MockReporter
val config: ConfigurationRegistry = SpyConfiguration.createSpyConfig
coreConfiguration = config.getConfig(classOf[CoreConfiguration])
tracer = new ElasticApmTracerBuilder().configurationRegistry(config).reporter(reporter).build
ElasticApmAgent.initInstrumentation(tracer, ByteBuddyAgent.install)
transaction = tracer.startRootTransaction(null).withName("Transaction").activate()
Comment thread
milanvdm marked this conversation as resolved.
Outdated
}

override def afterEach(context: AfterEach): Unit = ElasticApmAgent.reset()

test("Scala Future should propagate the tracing-context correctly across different threads") {
implicit val executionContext: ExecutionContextExecutor =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1))

Future("Test")
.map(_.length)
.flatMap(l => Future(l * 2))
.map(_.toString)
.flatMap(s => Future(s"$s-$s"))
.map(_ => tracer.currentTransaction().addCustomContext("future", true))
.map { _ =>
transaction.deactivate().end()
assertEquals(
reporter.getTransactions.get(0).getContext.getCustom("future").asInstanceOf[Boolean],
true
)
}
}

test("Worker thread should correctly set context on the current transaction") {
implicit val executionContext: ExecutionContextExecutor =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1))

new TestFutureTraceMethods().invokeAsync(tracer)
transaction.deactivate().end()
assertEquals(reporter.getTransactions.size(), 1)
assertEquals(reporter.getSpans.size(), 0)
assertEquals(
reporter.getTransactions.get(0).getContext.getCustom("future").asInstanceOf[Boolean],
true
)
}

test("Multiple async operations should be able to set context on the current transaction") {

implicit val multiPoolEc: ExecutionContextExecutor =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(3))

Future
.traverse(1 to 100) { _ =>
Future.sequence(List(
Future {
Thread.sleep(25)
tracer.currentTransaction().addCustomContext("future1", true)
},
Future {
Thread.sleep(50)
tracer.currentTransaction().addCustomContext("future2", true)
},
Future {
Thread.sleep(10)
tracer.currentTransaction().addCustomContext("future3", true)
}
))
}
.map { _ =>
transaction.deactivate().end()
assertEquals(
reporter.getTransactions.get(0).getContext.getCustom("future1").asInstanceOf[Boolean],
true
)
assertEquals(
reporter.getTransactions.get(0).getContext.getCustom("future2").asInstanceOf[Boolean],
true
)
assertEquals(
reporter.getTransactions.get(0).getContext.getCustom("future3").asInstanceOf[Boolean],
true
)
}

}

}

private class TestFutureTraceMethods {

/**
* Calling this method results in this method call tree:
*
* main thread | worker thread
* -------------------------------------------------------------------------------------------
* invokeAsync |
* | |
* --- blockingMethodOnMainThread |
* | |
* --- nonBlockingMethodOnMainThread |
* | |
* --------------------------> methodOnWorkerThread
* | |
* | --- longMethod
* |
*/
def invokeAsync(tracer: ElasticApmTracer)(implicit ec: ExecutionContext): Unit = blockingMethodOnMainThread(tracer)

private def blockingMethodOnMainThread(tracer: ElasticApmTracer)(implicit ec: ExecutionContext): Unit = {
try {
Await.result(nonBlockingMethodOnMainThread(tracer), 10.seconds)
} catch {
case e: Exception => e.printStackTrace()
}
}

private def nonBlockingMethodOnMainThread(tracer: ElasticApmTracer)(implicit ec: ExecutionContext): Future[Unit] =
Future(methodOnWorkerThread(tracer))

private def methodOnWorkerThread(tracer: ElasticApmTracer): Unit = longMethod(tracer)

private def longMethod(tracer: ElasticApmTracer): Unit = {
try {
Thread.sleep(100)
tracer.currentTransaction().addCustomContext("future", true)
} catch {
case e: InterruptedException => e.printStackTrace()
}
}

}
12 changes: 12 additions & 0 deletions apm-agent-plugins/apm-servlet-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>

<parent>
<artifactId>apm-agent-plugins</artifactId>
Expand Down
1 change: 1 addition & 0 deletions apm-agent-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<module>apm-jms-plugin</module>
<module>apm-hibernate-search-plugin</module>
<module>apm-redis-plugin</module>
<module>apm-scala-concurrent-plugin</module>
<module>apm-error-logging-plugin</module>
<module>apm-jmx-plugin</module>
<module>apm-mule4-plugin</module>
Expand Down
Loading