Merge pull request #996 from DataDog/landerson/couchbase-op-id

Add operation id and network tags to Couchbase
This commit is contained in:
Laplie Anderson 2019-09-19 15:56:59 -04:00 committed by GitHub
commit e6c061f841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 464 additions and 114 deletions

View File

@ -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'

View File

@ -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

View File

@ -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")

View File

@ -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))
} }
} }
} }

View File

@ -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)
} }
} }

View File

@ -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: '+'
}

View File

@ -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());
}
}
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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);
} }
} }

View File

@ -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")

View File

@ -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'