Skip to content

Commit fb33a60

Browse files
authored
Merge pull request #954 from ivantopo/issue-#798/instrument-4x-cassandra-driver
bring tracing support for Cassandra 4.x driver, fixes #798
2 parents cf7aecb + 48d84b2 commit fb33a60

File tree

13 files changed

+277
-29
lines changed

13 files changed

+277
-29
lines changed

build.sbt

+1-10
Original file line numberDiff line numberDiff line change
@@ -310,16 +310,7 @@ lazy val `kamon-cassandra` = (project in file("instrumentation/kamon-cassandra")
310310
.disablePlugins(AssemblyPlugin)
311311
.enablePlugins(JavaAgent)
312312
.settings(instrumentationSettings)
313-
.settings(
314-
libraryDependencies ++= Seq(
315-
kanelaAgent % "provided",
316-
"com.datastax.cassandra" % "cassandra-driver-core" % "3.6.0" % "provided",
317-
"org.apache.cassandra" % "cassandra-all" % "3.11.2" % "provided",
318-
scalatest % "test",
319-
logbackClassic % "test",
320-
"org.cassandraunit" % "cassandra-unit" % "3.11.2.0" % "test"
321-
)
322-
).dependsOn(`kamon-core`, `kamon-instrumentation-common`, `kamon-testkit` % "test", `kamon-executors`)
313+
.dependsOn(`kamon-core`, `kamon-instrumentation-common`, `kamon-testkit` % "test", `kamon-executors`)
323314

324315
lazy val `kamon-elasticsearch` = (project in file("instrumentation/kamon-elasticsearch"))
325316
.disablePlugins(AssemblyPlugin)
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
lazy val Cassandra3xTest = config("testCas3") extend(Test)
2+
lazy val Cassandra4xTest = config("testCas4") extend(Test)
3+
4+
val cassandra3xDriverVersion = "3.10.0"
5+
val cassandra4xDriverVersion = "4.10.0"
6+
7+
libraryDependencies ++= Seq(
8+
kanelaAgent % "provided",
9+
scalatest % "test",
10+
logbackClassic % "test",
11+
"org.testcontainers" % "cassandra" % "1.15.2" % "test"
12+
)
13+
14+
libraryDependencies ++= Seq(
15+
"com.datastax.cassandra" % "cassandra-driver-core" % cassandra3xDriverVersion % "provided,testCas3"
16+
)
17+
18+
libraryDependencies ++= Seq(
19+
"com.datastax.oss" % "java-driver-core" % cassandra4xDriverVersion % "provided,testCas4",
20+
"com.datastax.oss" % "java-driver-query-builder" % cassandra4xDriverVersion % "provided,testCas4"
21+
)
22+
23+
configs(Cassandra3xTest, Cassandra4xTest)
24+
inConfig(Cassandra3xTest)(Defaults.testSettings)
25+
inConfig(Cassandra4xTest)(Defaults.testSettings)
26+
27+
test in Test := {
28+
(test in Cassandra3xTest).value
29+
(test in Cassandra4xTest).value
30+
}

instrumentation/kamon-cassandra/src/main/resources/reference.conf

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ kanela {
5454
]
5555

5656
within = [
57-
"com.datastax.driver.core..*"
57+
"com.datastax.driver.core..*",
58+
"com.datastax.oss.driver.internal.core..*"
5859
]
5960
}
6061
}
61-
}
62+
}

instrumentation/kamon-cassandra/src/main/scala/com/datastax/driver/core/ExecutionAdvices.scala

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import kanela.agent.libs.net.bytebuddy.asm.Advice
3333

3434
object QueryOperations {
3535
val QueryOperationName = "cassandra.query"
36+
val BatchOperationName = "cassandra.batch"
3637
val QueryPrepareOperationName: String = QueryOperationName + ".prepare"
3738
val ExecutionOperationName: String = QueryOperationName + ".execution"
3839
}

instrumentation/kamon-cassandra/src/main/scala/kamon/instrumentation/cassandra/driver/DriverInstrumentation.scala

+74-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,19 @@ package cassandra
2020
package driver
2121

2222
import com.datastax.driver.core._
23+
import com.datastax.oss.driver.api.core.cql
24+
import com.datastax.oss.driver.api.core.cql.PrepareRequest
25+
import com.datastax.oss.driver.internal.core.cql.{CqlPrepareHandler, CqlRequestHandler}
2326
import kamon.instrumentation.cassandra.driver.DriverInstrumentation.ClusterManagerBridge
2427
import kamon.instrumentation.cassandra.metrics.HasPoolMetrics
25-
import kamon.instrumentation.context.HasContext.MixinWithInitializer
28+
import kamon.instrumentation.context.HasContext
29+
import kamon.trace.Span
2630
import kanela.agent.api.instrumentation.InstrumentationBuilder
2731
import kanela.agent.api.instrumentation.bridge.FieldBridge
32+
import kanela.agent.libs.net.bytebuddy.asm.Advice
33+
34+
import java.util.concurrent.CompletionStage
35+
import java.util.function.BiConsumer
2836

2937
class DriverInstrumentation extends InstrumentationBuilder {
3038

@@ -63,10 +71,10 @@ class DriverInstrumentation extends InstrumentationBuilder {
6371
.advise(method("onException"), OnExceptionAdvice)
6472
.advise(method("onTimeout"), OnTimeoutAdvice)
6573
.advise(method("onSet"), OnSetAdvice)
66-
.mixin(classOf[MixinWithInitializer])
74+
.mixin(classOf[HasContext.MixinWithInitializer])
6775

6876
onSubTypesOf("com.datastax.driver.core.Message$Response")
69-
.mixin(classOf[MixinWithInitializer])
77+
.mixin(classOf[HasContext.MixinWithInitializer])
7078

7179
onType("com.datastax.driver.core.ArrayBackedResultSet")
7280
.advise(method("fromMessage"), OnResultSetConstruction)
@@ -76,7 +84,7 @@ class DriverInstrumentation extends InstrumentationBuilder {
7684
* we need to carry parent-span id through result sets
7785
*/
7886
onType("com.datastax.driver.core.ArrayBackedResultSet$MultiPage")
79-
.mixin(classOf[MixinWithInitializer])
87+
.mixin(classOf[HasContext.MixinWithInitializer])
8088
onType("com.datastax.driver.core.ArrayBackedResultSet$MultiPage")
8189
.advise(method("queryNextPage"), OnFetchMore)
8290

@@ -88,6 +96,17 @@ class DriverInstrumentation extends InstrumentationBuilder {
8896
.mixin(classOf[HasPoolMetrics.Mixin])
8997
.advise(method("setLocationInfo"), HostLocationAdvice)
9098

99+
100+
/**
101+
* Cassandra Driver 4.10 support
102+
*/
103+
onTypes(
104+
"com.datastax.oss.driver.internal.core.cql.CqlPrepareHandler",
105+
"com.datastax.oss.driver.internal.core.cql.CqlRequestHandler")
106+
.mixin(classOf[HasContext.Mixin])
107+
.advise(isConstructor(), OnRequestHandlerConstructorAdvice)
108+
.advise(method("onThrottleReady"), OnThrottleReadyAdvice)
109+
91110
}
92111

93112
object DriverInstrumentation {
@@ -96,3 +115,54 @@ object DriverInstrumentation {
96115
def getClusterName: String
97116
}
98117
}
118+
119+
object OnRequestHandlerConstructorAdvice {
120+
121+
@Advice.OnMethodExit()
122+
def exit(@Advice.This requestHandler: HasContext, @Advice.Argument(0) req: Any): Unit = {
123+
val (operationName, statement) = req match {
124+
case pr: PrepareRequest => (QueryOperations.QueryPrepareOperationName, pr.getQuery())
125+
case ss: cql.SimpleStatement => (QueryOperations.QueryOperationName, ss.getQuery())
126+
case bs: cql.BoundStatement => (QueryOperations.QueryOperationName, bs.getPreparedStatement.getQuery())
127+
case bs: cql.BatchStatement => (QueryOperations.BatchOperationName, "")
128+
}
129+
130+
// Make that every case added to the "onTypes" clause for the Cassandra 4.x support
131+
// is also handled in this match.
132+
val resultStage: CompletionStage[_] = requestHandler match {
133+
case cph: CqlPrepareHandler => cph.handle()
134+
case crh: CqlRequestHandler => crh.handle()
135+
}
136+
137+
val clientSpan = Kamon.clientSpanBuilder(operationName, "cassandra.driver")
138+
.tag("db.type", "cassandra")
139+
.tag("db.statement", statement)
140+
.start()
141+
142+
/**
143+
* We are registering a callback on the result CompletionStage because the setFinalResult and
144+
* setFinalError methods might be called more than once on the same request handler.
145+
*/
146+
resultStage.whenComplete(new BiConsumer[Any, Throwable] {
147+
override def accept(result: Any, error: Throwable): Unit = {
148+
if(error != null) {
149+
clientSpan
150+
.fail(error)
151+
.finish()
152+
}
153+
else {
154+
clientSpan.finish()
155+
}
156+
}
157+
})
158+
}
159+
}
160+
161+
object OnThrottleReadyAdvice {
162+
163+
@Advice.OnMethodEnter()
164+
def enter(@Advice.This requestHandler: HasContext): Unit = {
165+
val querySpan = requestHandler.context.get(Span.Key)
166+
querySpan.mark("cassandra.throttle.ready")
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
kamon.instrumentation.cassandra {
22
tracing.create-round-trip-spans = yes
33
metrics.track-node-connection-pools = yes
4-
}
4+
}

instrumentation/kamon-cassandra/src/test/resources/logback.xml renamed to instrumentation/kamon-cassandra/src/testCas3/resources/logback.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
<root level="INFO">
1414
<appender-ref ref="STDOUT"/>
1515
</root>
16-
</configuration>
16+
</configuration>
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import kamon.instrumentation.cassandra.NodeConnectionPoolMetrics.NodeConnectionP
2222
import kamon.instrumentation.executor.ExecutorMetrics
2323
import kamon.tag.TagSet
2424
import kamon.testkit.{InstrumentInspection, MetricInspection}
25-
import org.cassandraunit.utils.EmbeddedCassandraServerHelper
2625
import org.scalatest.concurrent.Eventually
2726
import org.scalatest.time.SpanSugar
2827
import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec}
28+
import org.testcontainers.containers.CassandraContainer
2929

3030
class CassandraClientMetricsSpec
3131
extends WordSpec
@@ -111,11 +111,12 @@ class CassandraClientMetricsSpec
111111
}
112112

113113
var session: Session = _
114+
val cassandra = new CassandraContainer("cassandra:3.11.10")
114115

115-
override protected def beforeAll(): Unit = {
116-
EmbeddedCassandraServerHelper.startEmbeddedCassandra(40000L)
117-
session = EmbeddedCassandraServerHelper.getCluster.newSession()
118116

117+
override protected def beforeAll(): Unit = {
118+
cassandra.start()
119+
session = cassandra.getCluster.newSession()
119120
val keyspace = s"keyspaceMetricSpec"
120121

121122
session.execute(
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ package kamon.instrumentation.instrumentation
1818
import com.datastax.driver.core.exceptions.DriverException
1919
import com.datastax.driver.core.querybuilder.QueryBuilder
2020
import com.datastax.driver.core.{QueryOperations, Session}
21-
import kamon.module.Module.Registration
2221
import kamon.tag.Lookups._
2322
import kamon.testkit.{InstrumentInspection, MetricInspection, Reconfigure, TestSpanReporter}
24-
import org.cassandraunit.utils.EmbeddedCassandraServerHelper
2523
import org.scalatest.concurrent.Eventually
2624
import org.scalatest.time.SpanSugar
2725
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Matchers, OptionValues, WordSpec}
26+
import org.testcontainers.containers.CassandraContainer
2827

2928
import scala.collection.JavaConverters._
3029

@@ -118,17 +117,17 @@ class CassandraClientTracingInstrumentationSpec
118117
}
119118
}
120119

121-
var registration: Registration = _
122-
var session: Session = _
120+
var session: Session = _
121+
val cassandra = new CassandraContainer("cassandra:3.11.10")
123122

124123
override protected def beforeAll(): Unit = {
125124
enableFastSpanFlushing()
126125
sampleAlways()
127126

128127
val keyspace = s"keyspaceTracingSpec"
129128

130-
EmbeddedCassandraServerHelper.startEmbeddedCassandra(40000L)
131-
session = EmbeddedCassandraServerHelper.getCluster.newSession()
129+
cassandra.start()
130+
session = cassandra.getCluster.newSession()
132131

133132
session.execute(
134133
s"create keyspace $keyspace with replication = {'class':'SimpleStrategy', 'replication_factor':3}"
@@ -137,7 +136,7 @@ class CassandraClientTracingInstrumentationSpec
137136
session.execute(s"USE $keyspace")
138137

139138
session.execute("create table users (id uuid primary key, name text )")
140-
for (i <- 1 to 12) {
139+
for (_ <- 1 to 12) {
141140
session.execute("insert into users (id, name) values (uuid(), 'kamon')")
142141
}
143142
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kamon.instrumentation.cassandra {
2+
tracing.create-round-trip-spans = yes
3+
metrics.track-node-connection-pools = yes
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<configuration>
2+
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<encoder>
5+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
6+
</encoder>
7+
</appender>
8+
9+
<logger name="org.hyperic" level="OFF"/>
10+
<logger name="org.cassandraunit" level="OFF"/>
11+
<logger name="org.apache" level="OFF"/>
12+
13+
<root level="WARN">
14+
<appender-ref ref="STDOUT"/>
15+
</root>
16+
</configuration>

0 commit comments

Comments
 (0)