Skip to content

Commit d95d11e

Browse files
authored
Merge pull request #157 from DataDog/ark/cassandra_bytebuddy
cassandra bytebuddy
2 parents 37f70db + b6d9c13 commit d95d11e

File tree

11 files changed

+200
-109
lines changed

11 files changed

+200
-109
lines changed

buildSrc/src/main/groovy/VersionScanPlugin.groovy

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ class VersionScanPlugin implements Plugin<Project> {
3939
description = "Queries for all versions of configured modules and finds key classes"
4040
}
4141

42-
def hasRelevantTask = project.gradle.startParameter.taskNames.contains('scanVersions')
43-
hasRelevantTask |= project.gradle.startParameter.taskNames.contains('scanVersionsReport')
44-
hasRelevantTask |= project.gradle.startParameter.taskNames.contains('verifyVersionScan')
42+
def hasRelevantTask = project.gradle.startParameter.taskNames.any { it.contains('scanVersions') }
43+
hasRelevantTask |= project.gradle.startParameter.taskNames.any { it.contains('scanVersionsReport') }
44+
hasRelevantTask |= project.gradle.startParameter.taskNames.any { it.contains('verifyVersionScan') }
4545

4646
if (!hasRelevantTask) {
4747
return
@@ -80,7 +80,7 @@ class VersionScanPlugin implements Plugin<Project> {
8080
}
8181
}
8282
}
83-
if (project.gradle.startParameter.taskNames.contains('scanVersions')) {
83+
if (project.gradle.startParameter.taskNames.any { it.contains('scanVersions') }) {
8484
scanVersions.finalizedBy(scanVersionsReport)
8585
}
8686

@@ -176,7 +176,7 @@ class VersionScanPlugin implements Plugin<Project> {
176176
}
177177
}
178178

179-
if (project.gradle.startParameter.taskNames.contains('scanVersions')) {
179+
if (project.gradle.startParameter.taskNames.any { it.contains('scanVersions') }) {
180180
scanVersions.finalizedBy(verifyVersionScan)
181181
}
182182
}

dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/CassandraIntegrationTest.java

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,107 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44

5+
import com.datadoghq.trace.DDBaseSpan;
6+
import com.datadoghq.trace.DDTracer;
7+
import com.datadoghq.trace.writer.ListWriter;
58
import com.datastax.driver.core.Cluster;
69
import com.datastax.driver.core.Session;
7-
import java.io.IOException;
8-
import java.util.concurrent.ExecutionException;
9-
import org.apache.cassandra.exceptions.ConfigurationException;
10-
import org.apache.thrift.transport.TTransportException;
10+
import io.opentracing.Tracer;
11+
import io.opentracing.tag.Tags;
1112
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
12-
import org.junit.After;
13-
import org.junit.Before;
13+
import org.junit.AfterClass;
14+
import org.junit.Assert;
15+
import org.junit.BeforeClass;
1416
import org.junit.Test;
1517
import org.junit.experimental.categories.Category;
1618

17-
/** Created by gpolaert on 6/2/17. */
1819
@Category(ExpensiveTest.class)
1920
public class CassandraIntegrationTest {
21+
private static final ListWriter writer = new ListWriter();
22+
private static final Tracer tracer = new DDTracer(writer);
2023

21-
@Before
22-
public void start()
23-
throws InterruptedException, TTransportException, ConfigurationException, IOException {
24+
@BeforeClass
25+
public static void start() throws Exception {
2426
EmbeddedCassandraServerHelper.startEmbeddedCassandra(40000L);
27+
TestUtils.registerOrReplaceGlobalTracer(tracer);
2528
}
2629

27-
@After
28-
public void stop() {
30+
@AfterClass
31+
public static void stop() {
2932
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
3033
}
3134

3235
@Test
33-
public void testNewSessionSync() throws ClassNotFoundException {
36+
public void testSync() throws ClassNotFoundException {
3437
final Cluster cluster = EmbeddedCassandraServerHelper.getCluster();
3538
final Session session = cluster.newSession();
3639
assertThat(session.getClass().getName()).endsWith("contrib.cassandra.TracingSession");
40+
final int origSize = writer.getList().size();
41+
42+
session.execute("DROP KEYSPACE IF EXISTS sync_test");
43+
session.execute(
44+
"CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}");
45+
session.execute("CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )");
46+
session.execute("INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')");
47+
session.execute("SELECT * FROM sync_test.users where name = 'alice' ALLOW FILTERING");
48+
49+
assertThat(writer.getList().size()).isEqualTo(origSize + 5);
50+
DDBaseSpan<?> selectTrace = writer.get(writer.size() - 1).get(0);
51+
52+
assertThat(selectTrace.getServiceName()).isEqualTo(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME);
53+
assertThat(selectTrace.getOperationName()).isEqualTo("execute");
54+
assertThat(selectTrace.getResourceName()).isEqualTo("execute");
55+
56+
assertThat(selectTrace.getTags().get(Tags.COMPONENT.getKey())).isEqualTo("java-cassandra");
57+
assertThat(selectTrace.getTags().get(Tags.DB_STATEMENT.getKey()))
58+
.isEqualTo("SELECT * FROM sync_test.users where name = 'alice' ALLOW FILTERING");
59+
assertThat(selectTrace.getTags().get(Tags.DB_TYPE.getKey())).isEqualTo("cassandra");
60+
assertThat(selectTrace.getTags().get(Tags.PEER_HOSTNAME.getKey())).isEqualTo("localhost");
61+
// More info about IPv4 tag: https://trello.com/c/2el2IwkF/174-mongodb-ot-contrib-provides-a-wrong-peeripv4
62+
assertThat(selectTrace.getTags().get(Tags.PEER_HOST_IPV4.getKey())).isEqualTo(2130706433);
63+
assertThat(selectTrace.getTags().get(Tags.PEER_PORT.getKey())).isEqualTo(9142);
64+
assertThat(selectTrace.getTags().get(Tags.SPAN_KIND.getKey())).isEqualTo("client");
3765
}
3866

3967
@Test
40-
public void testNewSessionAsync()
41-
throws ClassNotFoundException, ExecutionException, InterruptedException {
68+
public void testAsync() throws Exception {
4269
final Cluster cluster = EmbeddedCassandraServerHelper.getCluster();
4370
final Session session = cluster.connectAsync().get();
4471
assertThat(session.getClass().getName()).endsWith("contrib.cassandra.TracingSession");
72+
final int origSize = writer.getList().size();
73+
74+
session.executeAsync("DROP KEYSPACE IF EXISTS async_test").get();
75+
session
76+
.executeAsync(
77+
"CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}")
78+
.get();
79+
session.executeAsync("CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )").get();
80+
session.executeAsync("INSERT INTO async_test.users (id, name) values (uuid(), 'alice')").get();
81+
session
82+
.executeAsync("SELECT * FROM async_test.users where name = 'alice' ALLOW FILTERING")
83+
.get();
84+
85+
// traces are finished on another thread, so we have some waiting logic
86+
for (int timeout = 0; writer.getList().size() < origSize + 5; timeout++) {
87+
if (timeout >= 10000) {
88+
Assert.fail("Cassandra async test timeout.");
89+
}
90+
Thread.sleep(1);
91+
}
92+
DDBaseSpan<?> selectTrace = writer.get(writer.size() - 1).get(0);
93+
94+
assertThat(selectTrace.getServiceName()).isEqualTo(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME);
95+
assertThat(selectTrace.getOperationName()).isEqualTo("execute");
96+
assertThat(selectTrace.getResourceName()).isEqualTo("execute");
97+
98+
assertThat(selectTrace.getTags().get(Tags.COMPONENT.getKey())).isEqualTo("java-cassandra");
99+
assertThat(selectTrace.getTags().get(Tags.DB_STATEMENT.getKey()))
100+
.isEqualTo("SELECT * FROM async_test.users where name = 'alice' ALLOW FILTERING");
101+
assertThat(selectTrace.getTags().get(Tags.DB_TYPE.getKey())).isEqualTo("cassandra");
102+
assertThat(selectTrace.getTags().get(Tags.PEER_HOSTNAME.getKey())).isEqualTo("localhost");
103+
// More info about IPv4 tag: https://trello.com/c/2el2IwkF/174-mongodb-ot-contrib-provides-a-wrong-peeripv4
104+
assertThat(selectTrace.getTags().get(Tags.PEER_HOST_IPV4.getKey())).isEqualTo(2130706433);
105+
assertThat(selectTrace.getTags().get(Tags.PEER_PORT.getKey())).isEqualTo(9142);
106+
assertThat(selectTrace.getTags().get(Tags.SPAN_KIND.getKey())).isEqualTo("client");
45107
}
46108
}

dd-java-agent/dd-java-agent.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ dependencies {
2424
compile(project(':dd-java-agent:integrations:aws-sdk')) {
2525
transitive = false
2626
}
27+
compile(project(':dd-java-agent:integrations:datastax-cassandra-3.2')) {
28+
transitive = false
29+
}
2730
compile(project(':dd-java-agent:integrations:jms-1')) {
2831
transitive = false
2932
}

dd-java-agent/integrations/cassandra/cassandra.gradle

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// TODO: VersionScan plugin does not report which version failed, which is making it hard to get meaningful results out of this block.
2+
// Once versionScan can report on which version failed, this can be enabled.
3+
// The desire is to apply the instrumentation to cassandra-datastax 2.3 and beyond.
4+
5+
apply plugin: 'version-scan'
6+
7+
versionScan {
8+
group = "com.datastax.cassandra"
9+
module = "cassandra-driver-core"
10+
versions = "[3.2.0,)"
11+
// verifyPresent = [
12+
// // class we're advising
13+
// 'com.datastax.driver.core.Cluster$Manager' : null,
14+
// // used by TracingSession
15+
// 'com.datastax.driver.core.BoundStatement' : null,
16+
// 'com.datastax.driver.core.BoundStatement' : null,
17+
// 'com.datastax.driver.core.CloseFuture' : null,
18+
// 'com.datastax.driver.core.Cluster' : null,
19+
// 'com.datastax.driver.core.Host' : null,
20+
// 'com.datastax.driver.core.PreparedStatement' : null,
21+
// 'com.datastax.driver.core.RegularStatement' : null,
22+
// 'com.datastax.driver.core.ResultSet' : null,
23+
// 'com.datastax.driver.core.ResultSetFuture' : null,
24+
// 'com.datastax.driver.core.Session' : null,
25+
// 'com.datastax.driver.core.Statement' : null,
26+
// 'com.google.common.base.Function' : null,
27+
// 'com.google.common.util.concurrent.Futures' : null,
28+
// 'com.google.common.util.concurrent.ListenableFuture': null
29+
// ]
30+
}
31+
32+
apply from: "${rootDir}/gradle/java.gradle"
33+
34+
dependencies {
35+
compile group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0'
36+
37+
compile project(':dd-trace')
38+
// include helpers to pick up opentracing-cassandra-driver helper
39+
compile project(':dd-java-agent:integrations:helpers')
40+
compile project(':dd-java-agent:tooling')
41+
42+
compile deps.bytebuddy
43+
compile deps.opentracing
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package dd.inst.datastax.cassandra;
2+
3+
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
4+
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
5+
import static net.bytebuddy.matcher.ElementMatchers.*;
6+
7+
import com.datastax.driver.core.Session;
8+
import com.google.auto.service.AutoService;
9+
import dd.trace.Instrumenter;
10+
import io.opentracing.Tracer;
11+
import io.opentracing.util.GlobalTracer;
12+
import java.lang.reflect.Constructor;
13+
import net.bytebuddy.agent.builder.AgentBuilder;
14+
import net.bytebuddy.asm.Advice;
15+
16+
@AutoService(Instrumenter.class)
17+
public class CassandraClientInstrumentation implements Instrumenter {
18+
@Override
19+
public AgentBuilder instrument(AgentBuilder agentBuilder) {
20+
return agentBuilder
21+
.type(
22+
named("com.datastax.driver.core.Cluster$Manager"),
23+
classLoaderHasClasses(
24+
"com.datastax.driver.core.BoundStatement",
25+
"com.datastax.driver.core.BoundStatement",
26+
"com.datastax.driver.core.CloseFuture",
27+
"com.datastax.driver.core.Cluster",
28+
"com.datastax.driver.core.Host",
29+
"com.datastax.driver.core.PreparedStatement",
30+
"com.datastax.driver.core.RegularStatement",
31+
"com.datastax.driver.core.ResultSet",
32+
"com.datastax.driver.core.ResultSetFuture",
33+
"com.datastax.driver.core.Session",
34+
"com.datastax.driver.core.Statement",
35+
"com.google.common.base.Function",
36+
"com.google.common.util.concurrent.Futures",
37+
"com.google.common.util.concurrent.ListenableFuture"))
38+
.transform(
39+
new AgentBuilder.Transformer.ForAdvice()
40+
.advice(
41+
isMethod().and(isPrivate()).and(named("newSession")).and(takesArguments(0)),
42+
CassandraClientAdvice.class.getName())
43+
.withExceptionHandler(defaultExceptionHandler()))
44+
.asDecorator();
45+
}
46+
47+
public static class CassandraClientAdvice {
48+
/**
49+
* Strategy: each time we build a connection to a Cassandra cluster, the
50+
* com.datastax.driver.core.Cluster$Manager.newSession() method is called. The opentracing
51+
* contribution is a simple wrapper, so we just have to wrap the new session.
52+
*
53+
* @param session The fresh session to patch
54+
* @return A new tracing session
55+
* @throws Exception
56+
*/
57+
@Advice.OnMethodExit(suppress = Throwable.class)
58+
public static void injectTracingSession(@Advice.Return(readOnly = false) Session session)
59+
throws Exception {
60+
if (session.getClass().getName().endsWith("contrib.cassandra.TracingSession")) {
61+
return;
62+
}
63+
64+
Class<?> clazz = Class.forName("io.opentracing.contrib.cassandra.TracingSession");
65+
Constructor<?> constructor = clazz.getDeclaredConstructor(Session.class, Tracer.class);
66+
constructor.setAccessible(true);
67+
session = (Session) constructor.newInstance(session, GlobalTracer.get());
68+
}
69+
}
70+
}

dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/CassandraHelper.java

Lines changed: 0 additions & 43 deletions
This file was deleted.

dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,6 @@ opentracing-aws-sdk:
2525
com.amazonaws.http.apache.utils.ApacheUtils:
2626
com.amazonaws.http.request.HttpRequestFactory:
2727

28-
opentracing-cassandra-driver:
29-
- artifact: cassandra-driver-core
30-
supported_version: 3\.2.*
31-
identifying_present_classes:
32-
com.datastax.driver.core.utils.MoreObjects:
33-
com.datastax.driver.core.RemoteEndpointAwareNettySSLOptions:
34-
com.datastax.driver.core.GuavaCompatibility:
35-
3628
opentracing-jms-2_producer:
3729
- artifact: javax.jms-api
3830
supported_version: 2\..*

dd-java-agent/src/main/resources/initializer-rules.btm

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,6 @@
1313
#ENDRULE
1414

1515

16-
# Instrument Cassandra client
17-
# ===========================
18-
RULE Cluster$Manager-init
19-
CLASS com.datastax.driver.core.Cluster$Manager
20-
METHOD <init>
21-
AT EXIT
22-
IF TRUE
23-
DO
24-
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
25-
ENDRULE
26-
27-
2816
# Instrument OkHttp
2917
# ===========================
3018
RULE OkHttpClient$Builder-init

dd-java-agent/src/main/resources/integration-rules.btm

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,6 @@ DO
2121
ENDRULE
2222

2323

24-
# Instrument Cassandra client
25-
# ===========================
26-
RULE opentracing-cassandra-driver
27-
CLASS com.datastax.driver.core.Cluster$Manager
28-
METHOD newSession()
29-
HELPER com.datadoghq.agent.integration.CassandraHelper
30-
AT EXIT
31-
IF TRUE
32-
DO
33-
$! = patch($!);
34-
ENDRULE
35-
36-
3724
# Instrument OkHttp
3825
# ===========================
3926
RULE opentracing-okhttp3

0 commit comments

Comments
 (0)