Merge pull request #996 from DataDog/landerson/couchbase-op-id
Add operation id and network tags to Couchbase
This commit is contained in:
commit
e6c061f841
|
@ -43,13 +43,22 @@ muzzle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
testArtifacts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test artifacts so couchbase-2.6 can reuse tests
|
||||||
|
artifacts {
|
||||||
|
testArtifacts testJar
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':dd-java-agent:instrumentation:rxjava-1')
|
compile project(':dd-java-agent:instrumentation:rxjava-1')
|
||||||
|
|
||||||
compileOnly group: 'com.couchbase.client', name: 'java-client', version: '2.0.0'
|
|
||||||
|
|
||||||
testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
|
|
||||||
|
|
||||||
|
compileOnly group: 'com.couchbase.client', name: 'java-client', version: '2.0.0'
|
||||||
|
|
||||||
|
testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
|
||||||
|
|
||||||
testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '2.0.0.RELEASE'
|
testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '2.0.0.RELEASE'
|
||||||
// Earliest version that seems to allow queries with CouchbaseMock:
|
// Earliest version that seems to allow queries with CouchbaseMock:
|
||||||
testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.5.0'
|
testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.5.0'
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.couchbase.client.java.document.JsonDocument
|
||||||
import com.couchbase.client.java.document.json.JsonObject
|
import com.couchbase.client.java.document.json.JsonObject
|
||||||
import com.couchbase.client.java.env.CouchbaseEnvironment
|
import com.couchbase.client.java.env.CouchbaseEnvironment
|
||||||
import com.couchbase.client.java.query.N1qlQuery
|
import com.couchbase.client.java.query.N1qlQuery
|
||||||
|
import spock.lang.Unroll
|
||||||
import spock.util.concurrent.BlockingVariable
|
import spock.util.concurrent.BlockingVariable
|
||||||
import util.AbstractCouchbaseTest
|
import util.AbstractCouchbaseTest
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ import java.util.concurrent.TimeUnit
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
@Unroll
|
||||||
class CouchbaseAsyncClientTest extends AbstractCouchbaseTest {
|
class CouchbaseAsyncClientTest extends AbstractCouchbaseTest {
|
||||||
static final int TIMEOUT = 10
|
static final int TIMEOUT = 10
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,13 @@ import com.couchbase.client.java.document.JsonDocument
|
||||||
import com.couchbase.client.java.document.json.JsonObject
|
import com.couchbase.client.java.document.json.JsonObject
|
||||||
import com.couchbase.client.java.env.CouchbaseEnvironment
|
import com.couchbase.client.java.env.CouchbaseEnvironment
|
||||||
import com.couchbase.client.java.query.N1qlQuery
|
import com.couchbase.client.java.query.N1qlQuery
|
||||||
|
import spock.lang.Unroll
|
||||||
import util.AbstractCouchbaseTest
|
import util.AbstractCouchbaseTest
|
||||||
|
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
@Unroll
|
||||||
class CouchbaseClientTest extends AbstractCouchbaseTest {
|
class CouchbaseClientTest extends AbstractCouchbaseTest {
|
||||||
def "test hasBucket #type"() {
|
def "test hasBucket #type"() {
|
||||||
when:
|
when:
|
||||||
|
@ -41,44 +43,6 @@ class CouchbaseClientTest extends AbstractCouchbaseTest {
|
||||||
// Connect to the bucket and open it
|
// Connect to the bucket and open it
|
||||||
Bucket bkt = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
|
Bucket bkt = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
|
||||||
|
|
||||||
// Create a JSON document and store it with the ID "helloworld"
|
|
||||||
JsonObject content = JsonObject.create().put("hello", "world")
|
|
||||||
def inserted = bkt.upsert(JsonDocument.create("helloworld", content))
|
|
||||||
def found = bkt.get("helloworld")
|
|
||||||
|
|
||||||
then:
|
|
||||||
found == inserted
|
|
||||||
found.content().getString("hello") == "world"
|
|
||||||
|
|
||||||
sortAndAssertTraces(3) {
|
|
||||||
trace(0, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Cluster.openBucket")
|
|
||||||
}
|
|
||||||
trace(1, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.upsert", bucketSettings.name())
|
|
||||||
}
|
|
||||||
trace(2, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.get", bucketSettings.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
cluster?.disconnect()
|
|
||||||
environment.shutdown()
|
|
||||||
|
|
||||||
where:
|
|
||||||
bucketSettings << [bucketCouchbase, bucketMemcache]
|
|
||||||
|
|
||||||
environment = envBuilder(bucketSettings).build()
|
|
||||||
cluster = CouchbaseCluster.create(environment, Arrays.asList("127.0.0.1"))
|
|
||||||
type = bucketSettings.type().name()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test upsert and get #type under trace"() {
|
|
||||||
when:
|
|
||||||
// Connect to the bucket and open it
|
|
||||||
Bucket bkt = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
|
|
||||||
|
|
||||||
// Create a JSON document and store it with the ID "helloworld"
|
// Create a JSON document and store it with the ID "helloworld"
|
||||||
JsonObject content = JsonObject.create().put("hello", "world")
|
JsonObject content = JsonObject.create().put("hello", "world")
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,15 @@ import org.springframework.context.ConfigurableApplicationContext
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||||
import org.springframework.data.repository.CrudRepository
|
import org.springframework.data.repository.CrudRepository
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Unroll
|
||||||
import util.AbstractCouchbaseTest
|
import util.AbstractCouchbaseTest
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
@Unroll
|
||||||
class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
|
class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
|
||||||
private static final Closure<Doc> FIND
|
static final Closure<Doc> FIND
|
||||||
static {
|
static {
|
||||||
// This method is different in Spring Data 2+
|
// This method is different in Spring Data 2+
|
||||||
try {
|
try {
|
||||||
|
@ -68,6 +73,10 @@ class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
|
||||||
TEST_WRITER.clear()
|
TEST_WRITER.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def cleanup() {
|
||||||
|
repo.deleteAll()
|
||||||
|
}
|
||||||
|
|
||||||
def "test empty repo"() {
|
def "test empty repo"() {
|
||||||
when:
|
when:
|
||||||
def result = repo.findAll()
|
def result = repo.findAll()
|
||||||
|
@ -81,70 +90,94 @@ class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
|
||||||
assertCouchbaseCall(it, 0, "Bucket.query", bucketCouchbase.name())
|
assertCouchbaseCall(it, 0, "Bucket.query", bucketCouchbase.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
where:
|
|
||||||
indexName = "test-index"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def "test CRUD"() {
|
def "test save"() {
|
||||||
when:
|
setup:
|
||||||
def doc = new Doc()
|
def doc = new Doc()
|
||||||
|
|
||||||
then: // CREATE
|
|
||||||
repo.save(doc) == doc
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.upsert", bucketCouchbase.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TEST_WRITER.clear()
|
|
||||||
|
|
||||||
and: // RETRIEVE
|
|
||||||
FIND(repo, "1") == doc
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.get", bucketCouchbase.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TEST_WRITER.clear()
|
|
||||||
|
|
||||||
when:
|
when:
|
||||||
doc.data = "other data"
|
def result = repo.save(doc)
|
||||||
|
|
||||||
then: // UPDATE
|
|
||||||
repo.save(doc) == doc
|
|
||||||
repo.findAll().asList() == [doc]
|
|
||||||
|
|
||||||
assertTraces(3) {
|
|
||||||
trace(0, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.upsert", bucketCouchbase.name())
|
|
||||||
}
|
|
||||||
trace(1, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.query", bucketCouchbase.name())
|
|
||||||
}
|
|
||||||
trace(2, 1) {
|
|
||||||
assertCouchbaseCall(it, 0, "Bucket.get", bucketCouchbase.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TEST_WRITER.clear()
|
|
||||||
|
|
||||||
when: // DELETE
|
|
||||||
repo.delete("1")
|
|
||||||
|
|
||||||
then:
|
then:
|
||||||
!repo.findAll().iterator().hasNext()
|
result == doc
|
||||||
|
assertTraces(1) {
|
||||||
and:
|
|
||||||
assertTraces(2) {
|
|
||||||
trace(0, 1) {
|
trace(0, 1) {
|
||||||
assertCouchbaseCall(it, 0, "Bucket.remove", bucketCouchbase.name())
|
assertCouchbaseCall(it, 0, "Bucket.upsert", bucketCouchbase.name())
|
||||||
}
|
}
|
||||||
trace(1, 1) {
|
}
|
||||||
assertCouchbaseCall(it, 0, "Bucket.query", bucketCouchbase.name())
|
}
|
||||||
|
|
||||||
|
def "test save and retrieve"() {
|
||||||
|
setup:
|
||||||
|
def doc = new Doc()
|
||||||
|
def result
|
||||||
|
|
||||||
|
when:
|
||||||
|
runUnderTrace("someTrace") {
|
||||||
|
repo.save(doc)
|
||||||
|
result = FIND(repo, "1")
|
||||||
|
|
||||||
|
blockUntilChildSpansFinished(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
then: // RETRIEVE
|
||||||
|
result == doc
|
||||||
|
sortAndAssertTraces(1) {
|
||||||
|
trace(0, 3) {
|
||||||
|
basicSpan(it, 0, "someTrace")
|
||||||
|
assertCouchbaseCall(it, 2, "Bucket.upsert", bucketCouchbase.name(), span(0))
|
||||||
|
assertCouchbaseCall(it, 1, "Bucket.get", bucketCouchbase.name(), span(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test save and update"() {
|
||||||
|
setup:
|
||||||
|
def doc = new Doc()
|
||||||
|
|
||||||
|
when:
|
||||||
|
runUnderTrace("someTrace") {
|
||||||
|
repo.save(doc)
|
||||||
|
doc.data = "other data"
|
||||||
|
repo.save(doc)
|
||||||
|
|
||||||
|
blockUntilChildSpansFinished(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
then:
|
||||||
|
sortAndAssertTraces(1) {
|
||||||
|
trace(0, 3) {
|
||||||
|
basicSpan(it, 0, "someTrace")
|
||||||
|
assertCouchbaseCall(it, 1, "Bucket.upsert", bucketCouchbase.name(), span(0))
|
||||||
|
assertCouchbaseCall(it, 2, "Bucket.upsert", bucketCouchbase.name(), span(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "save and delete"() {
|
||||||
|
setup:
|
||||||
|
def doc = new Doc()
|
||||||
|
def result
|
||||||
|
|
||||||
|
when: // DELETE
|
||||||
|
runUnderTrace("someTrace") {
|
||||||
|
repo.save(doc)
|
||||||
|
repo.delete("1")
|
||||||
|
result = repo.findAll().iterator().hasNext()
|
||||||
|
|
||||||
|
blockUntilChildSpansFinished(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
assert !result
|
||||||
|
sortAndAssertTraces(1) {
|
||||||
|
trace(0, 4) {
|
||||||
|
basicSpan(it, 0, "someTrace")
|
||||||
|
assertCouchbaseCall(it, 3, "Bucket.upsert", bucketCouchbase.name(), span(0))
|
||||||
|
assertCouchbaseCall(it, 2, "Bucket.remove", bucketCouchbase.name(), span(0))
|
||||||
|
assertCouchbaseCall(it, 1, "Bucket.query", bucketCouchbase.name(), span(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,13 @@ import com.couchbase.client.java.cluster.ClusterManager
|
||||||
import com.couchbase.client.java.env.CouchbaseEnvironment
|
import com.couchbase.client.java.env.CouchbaseEnvironment
|
||||||
import org.springframework.data.couchbase.core.CouchbaseTemplate
|
import org.springframework.data.couchbase.core.CouchbaseTemplate
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Unroll
|
||||||
import util.AbstractCouchbaseTest
|
import util.AbstractCouchbaseTest
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
|
||||||
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
|
@Unroll
|
||||||
class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
|
class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
|
||||||
|
|
||||||
@Shared
|
@Shared
|
||||||
|
@ -51,19 +56,25 @@ class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
|
||||||
def "test write #name"() {
|
def "test write #name"() {
|
||||||
setup:
|
setup:
|
||||||
def doc = new Doc()
|
def doc = new Doc()
|
||||||
|
def result
|
||||||
|
|
||||||
when:
|
when:
|
||||||
template.save(doc)
|
runUnderTrace("someTrace") {
|
||||||
def result = template.findById("1", Doc)
|
template.save(doc)
|
||||||
|
result = template.findById("1", Doc)
|
||||||
|
|
||||||
|
blockUntilChildSpansFinished(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
then:
|
then:
|
||||||
result != null
|
result != null
|
||||||
assertTraces(2) {
|
|
||||||
trace(0, 1) {
|
sortAndAssertTraces(1) {
|
||||||
assertCouchbaseCall(it, 0, "Bucket.upsert", name)
|
trace(0, 3) {
|
||||||
}
|
basicSpan(it, 0, "someTrace")
|
||||||
trace(1, 1) {
|
assertCouchbaseCall(it, 2, "Bucket.upsert", name, span(0))
|
||||||
assertCouchbaseCall(it, 0, "Bucket.get", name)
|
assertCouchbaseCall(it, 1, "Bucket.get", name, span(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,16 +88,20 @@ class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
|
||||||
def doc = new Doc()
|
def doc = new Doc()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
template.save(doc)
|
runUnderTrace("someTrace") {
|
||||||
template.remove(doc)
|
template.save(doc)
|
||||||
|
template.remove(doc)
|
||||||
|
|
||||||
|
blockUntilChildSpansFinished(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
then:
|
then:
|
||||||
assertTraces(2) {
|
sortAndAssertTraces(1) {
|
||||||
trace(0, 1) {
|
trace(0, 3) {
|
||||||
assertCouchbaseCall(it, 0, "Bucket.upsert", name)
|
basicSpan(it, 0, "someTrace")
|
||||||
}
|
assertCouchbaseCall(it, 2, "Bucket.upsert", name, span(0))
|
||||||
trace(1, 1) {
|
assertCouchbaseCall(it, 1, "Bucket.remove", name, span(0))
|
||||||
assertCouchbaseCall(it, 0, "Bucket.remove", name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Set properties before any plugins get loaded
|
||||||
|
ext {
|
||||||
|
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
// Version 2.7.5 and 2.7.8 were not released properly and muzzle cannot test against it causing failure.
|
||||||
|
// So we have to skip them resulting in this verbose setup.
|
||||||
|
fail {
|
||||||
|
group = 'com.couchbase.client'
|
||||||
|
module = 'java-client'
|
||||||
|
versions = "[,2.6.0)"
|
||||||
|
}
|
||||||
|
pass {
|
||||||
|
group = 'com.couchbase.client'
|
||||||
|
module = 'java-client'
|
||||||
|
versions = "[2.6.0,2.7.5)"
|
||||||
|
}
|
||||||
|
pass {
|
||||||
|
group = 'com.couchbase.client'
|
||||||
|
module = 'java-client'
|
||||||
|
versions = "[2.7.6,2.7.8)"
|
||||||
|
}
|
||||||
|
pass {
|
||||||
|
group = 'com.couchbase.client'
|
||||||
|
module = 'java-client'
|
||||||
|
versions = "[2.7.9,)"
|
||||||
|
}
|
||||||
|
fail {
|
||||||
|
group = 'com.couchbase.client'
|
||||||
|
module = 'couchbase-client'
|
||||||
|
versions = "(,)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':dd-java-agent:instrumentation:rxjava-1')
|
||||||
|
|
||||||
|
compileOnly group: 'com.couchbase.client', name: 'java-client', version: '2.6.0'
|
||||||
|
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:couchbase-2.0')
|
||||||
|
testCompile project(path: ':dd-java-agent:instrumentation:couchbase-2.0', configuration: 'testArtifacts')
|
||||||
|
testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
|
||||||
|
|
||||||
|
testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.1.0.RELEASE'
|
||||||
|
|
||||||
|
testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6.0'
|
||||||
|
testCompile group: 'com.couchbase.client', name: 'encryption', version: '1.0.0'
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.1+'
|
||||||
|
latestDepTestCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6+'
|
||||||
|
latestDepTestCompile group: 'com.couchbase.client', name: 'encryption', version: '+'
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package datadog.trace.instrumentation.couchbase.client;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
|
import com.couchbase.client.core.message.CouchbaseRequest;
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.InstrumentationContext;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class CouchbaseCoreInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public CouchbaseCoreInstrumentation() {
|
||||||
|
super("couchbase");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||||
|
return named("com.couchbase.client.core.CouchbaseCore");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> contextStore() {
|
||||||
|
return Collections.singletonMap(
|
||||||
|
"com.couchbase.client.core.message.CouchbaseRequest", Span.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(isPublic())
|
||||||
|
.and(takesArgument(0, named("com.couchbase.client.core.message.CouchbaseRequest")))
|
||||||
|
.and(named("send")),
|
||||||
|
CouchbaseCoreAdvice.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CouchbaseCoreAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||||
|
public static void addOperationIdToSpan(@Advice.Argument(0) final CouchbaseRequest request) {
|
||||||
|
|
||||||
|
final Scope scope = GlobalTracer.get().scopeManager().active();
|
||||||
|
if (scope != null) {
|
||||||
|
// The scope from the initial rxJava subscribe is not available to the networking layer
|
||||||
|
// To transfer the span, the span is added to the context store
|
||||||
|
|
||||||
|
final ContextStore<CouchbaseRequest, Span> contextStore =
|
||||||
|
InstrumentationContext.get(CouchbaseRequest.class, Span.class);
|
||||||
|
|
||||||
|
Span span = contextStore.get(request);
|
||||||
|
|
||||||
|
if (span == null) {
|
||||||
|
span = GlobalTracer.get().activeSpan();
|
||||||
|
contextStore.put(request, span);
|
||||||
|
|
||||||
|
if (request.operationId() != null) {
|
||||||
|
span.setTag("couchbase.operation_id", request.operationId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package datadog.trace.instrumentation.couchbase.client;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||||
|
|
||||||
|
import com.couchbase.client.core.message.CouchbaseRequest;
|
||||||
|
import com.couchbase.client.java.transcoder.crypto.JsonCryptoTranscoder;
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.ContextStore;
|
||||||
|
import datadog.trace.bootstrap.InstrumentationContext;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class CouchbaseNetworkInstrumentation extends Instrumenter.Default {
|
||||||
|
public CouchbaseNetworkInstrumentation() {
|
||||||
|
super("couchbase");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||||
|
// Exact class because private fields are used
|
||||||
|
return safeHasSuperType(named("com.couchbase.client.core.endpoint.AbstractGenericHandler"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> contextStore() {
|
||||||
|
return Collections.singletonMap(
|
||||||
|
"com.couchbase.client.core.message.CouchbaseRequest", Span.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
// encode(ChannelHandlerContext ctx, REQUEST msg, List<Object> out)
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(named("encode"))
|
||||||
|
.and(takesArguments(3))
|
||||||
|
.and(
|
||||||
|
takesArgument(
|
||||||
|
0, named("com.couchbase.client.deps.io.netty.channel.ChannelHandlerContext")))
|
||||||
|
.and(takesArgument(2, named("java.util.List"))),
|
||||||
|
CouchbaseNetworkAdvice.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CouchbaseNetworkAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void addNetworkTagsToSpan(
|
||||||
|
@Advice.FieldValue("remoteHostname") final String remoteHostname,
|
||||||
|
@Advice.FieldValue("remoteSocket") final String remoteSocket,
|
||||||
|
@Advice.FieldValue("localSocket") final String localSocket,
|
||||||
|
@Advice.Argument(1) final CouchbaseRequest request) {
|
||||||
|
final ContextStore<CouchbaseRequest, Span> contextStore =
|
||||||
|
InstrumentationContext.get(CouchbaseRequest.class, Span.class);
|
||||||
|
|
||||||
|
final Span span = contextStore.get(request);
|
||||||
|
if (span != null) {
|
||||||
|
Tags.PEER_HOSTNAME.set(span, remoteHostname);
|
||||||
|
|
||||||
|
if (remoteSocket != null) {
|
||||||
|
final int splitIndex = remoteSocket.lastIndexOf(":");
|
||||||
|
if (splitIndex != -1) {
|
||||||
|
Tags.PEER_PORT.set(span, Integer.valueOf(remoteSocket.substring(splitIndex + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.setTag("local.address", localSocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.6.0 and above
|
||||||
|
public static void muzzleCheck(final JsonCryptoTranscoder transcoder) {
|
||||||
|
transcoder.documentType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
|
|
||||||
|
class CouchbaseAsyncClient26Test extends CouchbaseAsyncClientTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
|
||||||
|
CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
|
|
||||||
|
class CouchbaseClient26Test extends CouchbaseClientTest {
|
||||||
|
@Override
|
||||||
|
void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
|
||||||
|
CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import datadog.opentracing.DDSpan
|
||||||
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import io.opentracing.tag.Tags
|
||||||
|
|
||||||
|
class CouchbaseSpanUtil {
|
||||||
|
// Reusable span assertion method. Cannot directly override AbstractCouchbaseTest.assertCouchbaseSpan because
|
||||||
|
// Of the class hierarchy of these tests
|
||||||
|
static void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
|
||||||
|
trace.span(index) {
|
||||||
|
serviceName "couchbase"
|
||||||
|
resourceName name
|
||||||
|
operationName "couchbase.call"
|
||||||
|
spanType DDSpanTypes.COUCHBASE
|
||||||
|
errored false
|
||||||
|
if (parentSpan == null) {
|
||||||
|
parent()
|
||||||
|
} else {
|
||||||
|
childOf((DDSpan) parentSpan)
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
"$Tags.COMPONENT.key" "couchbase-client"
|
||||||
|
"$Tags.DB_TYPE.key" "couchbase"
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
if (bucketName != null) {
|
||||||
|
"bucket" bucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because of caching, not all requests hit the server so these tags may be absent
|
||||||
|
"$Tags.PEER_HOSTNAME.key" { it == "localhost" || it == "127.0.0.1" || it == null }
|
||||||
|
"$Tags.PEER_PORT.key" { it == null || Number }
|
||||||
|
"local.address" { it == null || String }
|
||||||
|
|
||||||
|
// Not all couchbase operations have operation id. Notably, 'ViewQuery's do not
|
||||||
|
// We assign a resourceName of 'Bucket.query' and this is shared with n1ql queries
|
||||||
|
// that do have operation ids
|
||||||
|
"couchbase.operation_id" { it == null || String }
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package springdata
|
||||||
|
|
||||||
|
|
||||||
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
|
|
||||||
|
class CouchbaseSpringRepository26Test extends CouchbaseSpringRepositoryTest {
|
||||||
|
@Override
|
||||||
|
void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
|
||||||
|
CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package springdata
|
||||||
|
|
||||||
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
|
|
||||||
|
class CouchbaseSpringTemplate26Test extends CouchbaseSpringTemplateTest {
|
||||||
|
@Override
|
||||||
|
void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
|
||||||
|
CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -61,6 +62,7 @@ import spock.lang.Specification;
|
||||||
@SpecMetadata(filename = "AgentTestRunner.java", line = 0)
|
@SpecMetadata(filename = "AgentTestRunner.java", line = 0)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AgentTestRunner extends Specification {
|
public abstract class AgentTestRunner extends Specification {
|
||||||
|
private static final long TIMEOUT_MILLIS = 10 * 1000;
|
||||||
/**
|
/**
|
||||||
* For test runs, agent's global tracer will report to this list writer.
|
* For test runs, agent's global tracer will report to this list writer.
|
||||||
*
|
*
|
||||||
|
@ -206,10 +208,15 @@ public abstract class AgentTestRunner extends Specification {
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void blockUntilChildSpansFinished(final int numberOfSpans) {
|
public static void blockUntilChildSpansFinished(final int numberOfSpans) {
|
||||||
final Span span = io.opentracing.util.GlobalTracer.get().activeSpan();
|
final Span span = io.opentracing.util.GlobalTracer.get().activeSpan();
|
||||||
|
final long deadline = System.currentTimeMillis() + TIMEOUT_MILLIS;
|
||||||
if (span instanceof DDSpan) {
|
if (span instanceof DDSpan) {
|
||||||
final PendingTrace pendingTrace = ((DDSpan) span).context().getTrace();
|
final PendingTrace pendingTrace = ((DDSpan) span).context().getTrace();
|
||||||
|
|
||||||
while (pendingTrace.size() < numberOfSpans) {
|
while (pendingTrace.size() < numberOfSpans) {
|
||||||
|
if (System.currentTimeMillis() > deadline) {
|
||||||
|
throw new TimeoutException(
|
||||||
|
"Timed out waiting for child spans. Received: " + pendingTrace.size());
|
||||||
|
}
|
||||||
Thread.sleep(10);
|
Thread.sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.opentracing.Tracer
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||||
import static datadog.trace.api.Config.TRACE_CLASSES_EXCLUDE
|
import static datadog.trace.api.Config.TRACE_CLASSES_EXCLUDE
|
||||||
|
@ -67,6 +68,16 @@ class AgentTestRunnerTest extends AgentTestRunner {
|
||||||
bootstrapClassesIncorrectlyLoaded == []
|
bootstrapClassesIncorrectlyLoaded == []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "waiting for child spans times out"() {
|
||||||
|
when:
|
||||||
|
runUnderTrace("parent") {
|
||||||
|
blockUntilChildSpansFinished(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
thrown(TimeoutException)
|
||||||
|
}
|
||||||
|
|
||||||
def "logging works"() {
|
def "logging works"() {
|
||||||
when:
|
when:
|
||||||
org.slf4j.LoggerFactory.getLogger(AgentTestRunnerTest).debug("hello")
|
org.slf4j.LoggerFactory.getLogger(AgentTestRunnerTest).debug("hello")
|
||||||
|
|
|
@ -28,6 +28,7 @@ include ':dd-java-agent:instrumentation:apache-httpclient-4'
|
||||||
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
|
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
|
||||||
include ':dd-java-agent:instrumentation:aws-java-sdk-2.2'
|
include ':dd-java-agent:instrumentation:aws-java-sdk-2.2'
|
||||||
include ':dd-java-agent:instrumentation:couchbase-2.0'
|
include ':dd-java-agent:instrumentation:couchbase-2.0'
|
||||||
|
include ':dd-java-agent:instrumentation:couchbase-2.6'
|
||||||
include ':dd-java-agent:instrumentation:datastax-cassandra-3'
|
include ':dd-java-agent:instrumentation:datastax-cassandra-3'
|
||||||
include ':dd-java-agent:instrumentation:dropwizard'
|
include ':dd-java-agent:instrumentation:dropwizard'
|
||||||
include ':dd-java-agent:instrumentation:dropwizard:dropwizard-views'
|
include ':dd-java-agent:instrumentation:dropwizard:dropwizard-views'
|
||||||
|
|
Loading…
Reference in New Issue