Merge branch 'master' of github.com:DataDog/dd-trace-java into labbati/atlas-1.1.0-compatibility

This commit is contained in:
Luca Abbati 2019-07-26 11:23:06 +02:00
commit 8609a07eaf
No known key found for this signature in database
GPG Key ID: 74DBB952D9BA17F2
82 changed files with 2325 additions and 440 deletions

View File

@ -95,7 +95,7 @@ public class ClassLoaderMatcher {
/**
* TODO: this turns out to be useless with OSGi: {@code
* }org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when
* org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when
* class loading is issued from this check and {@code false} for 'real' class loads. We should
* come up with some sort of hack to avoid this problem.
*/

View File

@ -25,8 +25,16 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest<AkkaHttpClientDec
def response
try {
response = Http.get(system).singleRequest(request, materializer).toCompletableFuture().get()
response = Http.get(system)
.singleRequest(request, materializer)
//.whenComplete { result, error ->
// FIXME: Callback should be here instead.
// callback?.call()
//}
.toCompletableFuture()
.get()
} finally {
// FIXME: remove this when callback above works.
blockUntilChildSpansFinished(1)
}
callback?.call()

View File

@ -1,6 +1,5 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator
import io.opentracing.util.GlobalTracer
import org.apache.http.HttpResponse
import org.apache.http.concurrent.FutureCallback
import org.apache.http.impl.nio.client.HttpAsyncClients
@ -22,8 +21,6 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest<ApacheHttpAsyncCl
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def hasParent = GlobalTracer.get().activeSpan() != null
def request = new HttpUriRequest(method, uri)
headers.entrySet().each {
request.addHeader(new BasicHeader(it.key, it.value))
@ -35,21 +32,13 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest<ApacheHttpAsyncCl
@Override
void completed(HttpResponse result) {
if (hasParent && GlobalTracer.get().activeSpan() == null) {
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
} else {
responseFuture.complete(result.statusLine.statusCode)
}
responseFuture.complete(result.statusLine.statusCode)
callback?.call()
}
@Override
void failed(Exception ex) {
if (hasParent && GlobalTracer.get().activeSpan() == null) {
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
} else {
responseFuture.completeExceptionally(ex)
}
responseFuture.completeExceptionally(ex)
}
@Override

View File

@ -0,0 +1,34 @@
muzzle {
pass {
group = "com.google.http-client"
module = "google-http-client"
// 1.19.0 is the first release. The versions before are betas and RCs
versions = "[1.19.0,)"
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
compileOnly group: 'com.google.http-client', name: 'google-http-client', version: '1.19.0'
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile group: 'com.google.http-client', name: 'google-http-client', version: '1.19.0'
latestDepTestCompile group: 'com.google.http-client', name: 'google-http-client', version: '+'
}

View File

@ -0,0 +1,50 @@
package datadog.trace.instrumentation.googlehttpclient;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import java.net.URISyntaxException;
public class GoogleHttpClientDecorator extends HttpClientDecorator<HttpRequest, HttpResponse> {
public static final GoogleHttpClientDecorator DECORATE = new GoogleHttpClientDecorator();
@Override
protected String method(final HttpRequest httpRequest) {
return httpRequest.getRequestMethod();
}
@Override
protected URI url(final HttpRequest httpRequest) throws URISyntaxException {
// Google uses %20 (space) instead of "+" for spaces in the fragment
// Add "+" back for consistency with the other http client instrumentations
final String url = httpRequest.getUrl().build();
final String fixedUrl = url.replaceAll("%20", "+");
return new URI(fixedUrl);
}
@Override
protected String hostname(final HttpRequest httpRequest) {
return httpRequest.getUrl().getHost();
}
@Override
protected Integer port(final HttpRequest httpRequest) {
return httpRequest.getUrl().getPort();
}
@Override
protected Integer status(final HttpResponse httpResponse) {
return httpResponse.getStatusCode();
}
@Override
protected String[] instrumentationNames() {
return new String[] {"google-http-client"};
}
@Override
protected String component() {
return "google-http-client";
}
}

View File

@ -0,0 +1,193 @@
package datadog.trace.instrumentation.googlehttpclient;
import static datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator.DECORATE;
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 static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
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.log.Fields;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
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 GoogleHttpClientInstrumentation extends Instrumenter.Default {
public GoogleHttpClientInstrumentation() {
super("google-http-client");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
// HttpRequest is a final class. Only need to instrument it exactly
return named("com.google.api.client.http.HttpRequest");
}
@Override
public Map<String, String> contextStore() {
return Collections.singletonMap(
"com.google.api.client.http.HttpRequest", RequestState.class.getName());
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.HttpClientDecorator",
packageName + ".GoogleHttpClientDecorator",
packageName + ".RequestState",
getClass().getName() + "$GoogleHttpClientAdvice",
getClass().getName() + "$GoogleHttpClientAsyncAdvice",
getClass().getName() + "$HeadersInjectAdapter"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod().and(isPublic()).and(named("execute")).and(takesArguments(0)),
GoogleHttpClientAdvice.class.getName());
transformers.put(
isMethod()
.and(isPublic())
.and(named("executeAsync"))
.and(takesArguments(1))
.and(takesArgument(0, (named("java.util.concurrent.Executor")))),
GoogleHttpClientAsyncAdvice.class.getName());
return transformers;
}
public static class GoogleHttpClientAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(@Advice.This final HttpRequest request) {
final ContextStore<HttpRequest, RequestState> contextStore =
InstrumentationContext.get(HttpRequest.class, RequestState.class);
RequestState state = contextStore.get(request);
if (state == null) {
state = new RequestState(GlobalTracer.get().buildSpan("http.request").start());
contextStore.put(request, state);
}
final Span span = state.getSpan();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
DECORATE.afterStart(span);
DECORATE.onRequest(span, request);
GlobalTracer.get()
.inject(span.context(), Format.Builtin.HTTP_HEADERS, new HeadersInjectAdapter(request));
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.This final HttpRequest request,
@Advice.Return final HttpResponse response,
@Advice.Thrown final Throwable throwable) {
final ContextStore<HttpRequest, RequestState> contextStore =
InstrumentationContext.get(HttpRequest.class, RequestState.class);
final RequestState state = contextStore.get(request);
if (state != null) {
final Span span = state.getSpan();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
DECORATE.onResponse(span, response);
DECORATE.onError(span, throwable);
// If HttpRequest.setThrowExceptionOnExecuteError is set to false, there are no exceptions
// for a failed request. Thus, check the response code
if (response != null && !response.isSuccessStatusCode()) {
Tags.ERROR.set(span, true);
span.log(singletonMap(Fields.MESSAGE, response.getStatusMessage()));
}
DECORATE.beforeFinish(span);
span.finish();
}
}
}
}
public static class GoogleHttpClientAsyncAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(@Advice.This final HttpRequest request) {
final Span span = GlobalTracer.get().buildSpan("http.request").start();
final ContextStore<HttpRequest, RequestState> contextStore =
InstrumentationContext.get(HttpRequest.class, RequestState.class);
final RequestState state = new RequestState(span);
contextStore.put(request, state);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.This final HttpRequest request, @Advice.Thrown final Throwable throwable) {
if (throwable != null) {
final ContextStore<HttpRequest, RequestState> contextStore =
InstrumentationContext.get(HttpRequest.class, RequestState.class);
final RequestState state = contextStore.get(request);
if (state != null) {
final Span span = state.getSpan();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
span.finish();
}
}
}
}
}
public static class HeadersInjectAdapter implements TextMap {
private final HttpRequest request;
public HeadersInjectAdapter(final HttpRequest request) {
this.request = request;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException(
"This class should be used only with tracer#inject()");
}
@Override
public void put(final String key, final String value) {
request.getHeaders().put(key, value);
}
}
}

View File

@ -0,0 +1,10 @@
package datadog.trace.instrumentation.googlehttpclient;
import io.opentracing.Span;
import lombok.Data;
import lombok.NonNull;
@Data
public class RequestState {
@NonNull public Span span;
}

View File

@ -0,0 +1,69 @@
import com.google.api.client.http.GenericUrl
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpResponse
import com.google.api.client.http.javanet.NetHttpTransport
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.api.DDSpanTypes
import datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator
import spock.lang.Shared
abstract class AbstractGoogleHttpClientTest extends HttpClientTest<GoogleHttpClientDecorator> {
@Shared
def requestFactory = new NetHttpTransport().createRequestFactory()
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
doRequest(method, uri, headers, callback, false)
}
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback, boolean throwExceptionOnError) {
GenericUrl genericUrl = new GenericUrl(uri)
HttpRequest request = requestFactory.buildRequest(method, genericUrl, null)
request.getHeaders().putAll(headers)
request.setThrowExceptionOnExecuteError(throwExceptionOnError)
HttpResponse response = executeRequest(request)
callback?.call()
return response.getStatusCode()
}
abstract HttpResponse executeRequest(HttpRequest request)
@Override
GoogleHttpClientDecorator decorator() {
return GoogleHttpClientDecorator.DECORATE
}
@Override
boolean testRedirects() {
// Circular redirects don't throw an exception with Google Http Client
return false
}
def "error traces when exception is not thrown"() {
given:
def uri = server.address.resolve("/error")
when:
def status = doRequest(method, uri)
then:
status == 500
assertTraces(2) {
server.distributedRequestTrace(it, 0, trace(1).last())
trace(1, size(1)) {
span(0) {
resourceName "$method $uri.path"
spanType DDSpanTypes.HTTP_CLIENT
errored true
}
}
}
where:
method = "GET"
}
}

View File

@ -0,0 +1,9 @@
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpResponse
class GoogleHttpClientAsyncTest extends AbstractGoogleHttpClientTest {
@Override
HttpResponse executeRequest(HttpRequest request) {
return request.executeAsync().get()
}
}

View File

@ -0,0 +1,9 @@
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpResponse
class GoogleHttpClientTest extends AbstractGoogleHttpClientTest {
@Override
HttpResponse executeRequest(HttpRequest request) {
return request.execute()
}
}

View File

@ -4,6 +4,7 @@ import javax.ws.rs.client.AsyncInvoker
import javax.ws.rs.client.Client
import javax.ws.rs.client.ClientBuilder
import javax.ws.rs.client.Entity
import javax.ws.rs.client.InvocationCallback
import javax.ws.rs.client.WebTarget
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
@ -22,8 +23,16 @@ abstract class JaxRsClientAsyncTest extends HttpClientTest<JaxRsClientDecorator>
AsyncInvoker request = builder.async()
def body = BODY_METHODS.contains(method) ? Entity.text("") : null
Response response = request.method(method, (Entity) body).get()
callback?.call()
Response response = request.method(method, (Entity) body, new InvocationCallback<Response>(){
@Override
void completed(Response s) {
callback?.call()
}
@Override
void failed(Throwable throwable) {
}
}).get()
return response.status
}

View File

@ -295,7 +295,6 @@ public enum JDBCConnectionUrlParser {
MSSQLSERVER("microsoft", "sqlserver") {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 1433;
private static final String DEFAULT_INSTANCE = "MSSQLSERVER";
@Override
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
@ -307,9 +306,6 @@ public enum JDBCConnectionUrlParser {
}
builder.type("sqlserver");
final DBInfo dbInfo = builder.build();
if (dbInfo.getInstance() == null) {
builder.instance(DEFAULT_INSTANCE);
}
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}

View File

@ -88,12 +88,12 @@ class JDBCConnectionUrlParserTest extends Specification {
"address=(host=anotherhost)(port=3306)(user=wrong)(password=PW)/mdbdb?user=mdbuser&password=PW" | null | "mysql" | "replication" | "mdbuser" | "mdb.host" | 3306 | null | "mdbdb"
//https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | "MSSQLSERVER" | null
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | "MSSQLSERVER" | "stdDatabaseName"
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | null | null
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:sqlserver://ss.host\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "ssinstance" | "ssdb"
"jdbc:sqlserver://;serverName=ss.host\\ssinstance:44;DatabaseName=;" | null | "sqlserver" | null | null | "ss.host" | 44 | "ssinstance" | null
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | "MSSQLSERVER" | "ssdb"
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "MSSQLSERVER" | "ssdb"
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | null | "ssdb"
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | null | "ssdb"
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm

View File

@ -7,6 +7,7 @@ public class TracingIterable implements Iterable<ConsumerRecord> {
private final Iterable<ConsumerRecord> delegate;
private final String operationName;
private final KafkaDecorator decorator;
private boolean firstIterator = true;
public TracingIterable(
final Iterable<ConsumerRecord> delegate,
@ -19,6 +20,17 @@ public class TracingIterable implements Iterable<ConsumerRecord> {
@Override
public Iterator<ConsumerRecord> iterator() {
return new TracingIterator(delegate.iterator(), operationName, decorator);
Iterator<ConsumerRecord> it;
// We should only return one iterator with tracing.
// However, this is not thread-safe, but usually the first (hopefully only) traversal of
// ConsumerRecords is performed in the same thread that called poll()
if (this.firstIterator) {
it = new TracingIterator(delegate.iterator(), operationName, decorator);
firstIterator = false;
} else {
it = delegate.iterator();
}
return it;
}
}

View File

@ -1,23 +1,19 @@
package datadog.trace.instrumentation.kafka_clients;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.apache.kafka.clients.consumer.ConsumerRecord;
public class TracingList implements List<ConsumerRecord> {
public class TracingList extends TracingIterable implements List<ConsumerRecord> {
private final List<ConsumerRecord> delegate;
private final String operationName;
private final KafkaDecorator decorator;
public TracingList(
final List<ConsumerRecord> delegate,
final String operationName,
final KafkaDecorator decorator) {
super(delegate, operationName, decorator);
this.delegate = delegate;
this.operationName = operationName;
this.decorator = decorator;
}
@Override
@ -35,11 +31,6 @@ public class TracingList implements List<ConsumerRecord> {
return delegate.contains(o);
}
@Override
public Iterator<ConsumerRecord> iterator() {
return new TracingIterator(delegate.iterator(), operationName, decorator);
}
@Override
public Object[] toArray() {
return delegate.toArray();
@ -137,6 +128,10 @@ public class TracingList implements List<ConsumerRecord> {
@Override
public List<ConsumerRecord> subList(final int fromIndex, final int toIndex) {
return new TracingList(delegate.subList(fromIndex, toIndex), operationName, decorator);
// TODO: the API for subList is not really good to instrument it in context of Kafka
// Consumer so we will not do that for now
// Kafka is essentially a sequential commit log. We should only enable tracing when traversing
// sequentially with an iterator
return delegate.subList(fromIndex, toIndex);
}
}

View File

@ -1,6 +1,11 @@
import datadog.trace.agent.test.AgentTestRunner
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.junit.ClassRule
import org.apache.kafka.clients.consumer.KafkaConsumer
import org.apache.kafka.clients.producer.KafkaProducer
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.common.TopicPartition
import org.junit.Rule
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
import org.springframework.kafka.core.DefaultKafkaProducerFactory
import org.springframework.kafka.core.KafkaTemplate
@ -9,7 +14,6 @@ import org.springframework.kafka.listener.MessageListener
import org.springframework.kafka.test.rule.KafkaEmbedded
import org.springframework.kafka.test.utils.ContainerTestUtils
import org.springframework.kafka.test.utils.KafkaTestUtils
import spock.lang.Shared
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
@ -17,8 +21,7 @@ import java.util.concurrent.TimeUnit
class KafkaClientTest extends AgentTestRunner {
static final SHARED_TOPIC = "shared.topic"
@Shared
@ClassRule
@Rule
KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SHARED_TOPIC)
def "test kafka produce and consume"() {
@ -121,4 +124,83 @@ class KafkaClientTest extends AgentTestRunner {
container?.stop()
}
def "test records(TopicPartition) kafka consume"() {
setup:
// set up the Kafka consumer properties
def kafkaPartition = 0
def consumerProperties = KafkaTestUtils.consumerProps("sender", "false", embeddedKafka)
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
def consumer = new KafkaConsumer<String,String>(consumerProperties)
def senderProps = KafkaTestUtils.senderProps(embeddedKafka.getBrokersAsString())
def producer = new KafkaProducer(senderProps)
consumer.assign(Arrays.asList(new TopicPartition(SHARED_TOPIC, kafkaPartition)))
when:
def greeting = "Hello from MockConsumer!"
producer.send(new ProducerRecord<Integer, String>(SHARED_TOPIC, kafkaPartition, null, greeting))
then:
TEST_WRITER.waitForTraces(1)
def records = new LinkedBlockingQueue<ConsumerRecord<String, String>>()
def pollResult = KafkaTestUtils.getRecords(consumer)
def recs = pollResult.records(new TopicPartition(SHARED_TOPIC, kafkaPartition)).iterator()
def first = null
if (recs.hasNext()) {
first = recs.next()
}
then:
recs.hasNext() == false
first.value() == greeting
first.key() == null
assertTraces(2) {
trace(0, 1) {
// PRODUCER span 0
span(0) {
serviceName "kafka"
operationName "kafka.produce"
resourceName "Produce Topic $SHARED_TOPIC"
spanType "queue"
errored false
parent()
tags {
"component" "java-kafka"
"span.kind" "producer"
"kafka.partition" { it >= 0 }
defaultTags(true)
}
}
}
trace(1, 1) {
// CONSUMER span 0
span(0) {
serviceName "kafka"
operationName "kafka.consume"
resourceName "Consume Topic $SHARED_TOPIC"
spanType "queue"
errored false
childOf TEST_WRITER[0][0]
tags {
"component" "java-kafka"
"span.kind" "consumer"
"partition" { it >= 0 }
"offset" 0
defaultTags(true)
}
}
}
}
cleanup:
consumer.close()
producer.close()
}
}

View File

@ -32,6 +32,9 @@ public class AttributeKeys {
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
attributeKey(HttpClientTracingHandler.class.getName() + ".span");
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
attributeKey(HttpClientTracingHandler.class.getName() + ".parent");
/**
* Generate an attribute key or reuse the one existing in the global app map. This implementation
* creates attributes only once even if the current class is loaded by several class loaders and

View File

@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpRequest;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.util.GlobalTracer;
import java.net.InetSocketAddress;
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
final HttpRequest request = (HttpRequest) msg;
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
final Tracer tracer = GlobalTracer.get();
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan());
final Span span = tracer.buildSpan("netty.client.request").start();
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
DECORATE.afterStart(span);
DECORATE.onRequest(span, request);
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
// AWS calls are often signed, so we can't add headers without breaking the signature.
if (!request.headers().contains("amz-sdk-invocation-id")) {
GlobalTracer.get()
.inject(
span.context(),
Format.Builtin.HTTP_HEADERS,
new NettyResponseInjectAdapter(request));
tracer.inject(
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
}
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);

View File

@ -7,44 +7,38 @@ import datadog.trace.instrumentation.netty40.AttributeKeys;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.util.Attribute;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.noop.NoopSpan;
import io.opentracing.util.GlobalTracer;
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
final Attribute<Span> parentAttr =
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY);
parentAttr.setIfAbsent(NoopSpan.INSTANCE);
final Span parent = parentAttr.get();
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get();
if (span == null) {
ctx.fireChannelRead(msg);
return;
final boolean finishSpan = msg instanceof HttpResponse;
if (span != null && finishSpan) {
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
DECORATE.onResponse(span, (HttpResponse) msg);
DECORATE.beforeFinish(span);
span.finish();
}
}
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
final boolean finishSpan = msg instanceof HttpResponse;
// We want the callback in the scope of the parent, not the client span
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
try {
ctx.fireChannelRead(msg);
} catch (final Throwable throwable) {
if (finishSpan) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
Tags.HTTP_STATUS.set(span, 500);
span.finish(); // Finish the span manually since finishSpanOnClose was false
throw throwable;
}
}
if (finishSpan) {
DECORATE.onResponse(span, (HttpResponse) msg);
DECORATE.beforeFinish(span);
span.finish(); // Finish the span manually since finishSpanOnClose was false
}
ctx.fireChannelRead(msg);
}
}
}

View File

@ -1,8 +1,10 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator
import io.opentracing.tag.Tags
import org.asynchttpclient.AsyncCompletionHandler
import org.asynchttpclient.AsyncHttpClient
import org.asynchttpclient.DefaultAsyncHttpClientConfig
import org.asynchttpclient.Response
import spock.lang.Shared
import java.util.concurrent.ExecutionException
@ -25,8 +27,13 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
def methodName = "prepare" + method.toLowerCase().capitalize()
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
headers.each { requestBuilder.setHeader(it.key, it.value) }
def response = requestBuilder.execute().get()
callback?.call()
def response = requestBuilder.execute(new AsyncCompletionHandler() {
@Override
Object onCompleted(Response response) throws Exception {
callback?.call()
return response
}
}).get()
return response.statusCode
}
@ -66,7 +73,7 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
and:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", thrownException)
basicSpan(it, 0, "parent", null, thrownException)
span(1) {
operationName "netty.connect"

View File

@ -36,6 +36,9 @@ public class AttributeKeys {
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
attributeKey(HttpClientTracingHandler.class.getName() + ".span");
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
attributeKey(HttpClientTracingHandler.class.getName() + ".parent");
/**
* Generate an attribute key or reuse the one existing in the global app map. This implementation
* creates attributes only once even if the current class is loaded by several class loaders and

View File

@ -42,6 +42,7 @@ public class ChannelFutureListenerInstrumentation extends Instrumenter.Default {
public String[] helperClassNames() {
return new String[] {
packageName + ".AttributeKeys",
packageName + ".AttributeKeys$1",
"datadog.trace.agent.decorator.BaseDecorator",
// client helpers
"datadog.trace.agent.decorator.ClientDecorator",

View File

@ -56,6 +56,7 @@ public class NettyChannelPipelineInstrumentation extends Instrumenter.Default {
public String[] helperClassNames() {
return new String[] {
packageName + ".AttributeKeys",
packageName + ".AttributeKeys$1",
"datadog.trace.agent.decorator.BaseDecorator",
// client helpers
"datadog.trace.agent.decorator.ClientDecorator",

View File

@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpRequest;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.util.GlobalTracer;
import java.net.InetSocketAddress;
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
final HttpRequest request = (HttpRequest) msg;
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
final Tracer tracer = GlobalTracer.get();
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan());
final Span span = tracer.buildSpan("netty.client.request").start();
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
DECORATE.afterStart(span);
DECORATE.onRequest(span, request);
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
// AWS calls are often signed, so we can't add headers without breaking the signature.
if (!request.headers().contains("amz-sdk-invocation-id")) {
GlobalTracer.get()
.inject(
span.context(),
Format.Builtin.HTTP_HEADERS,
new NettyResponseInjectAdapter(request));
tracer.inject(
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
}
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);

View File

@ -7,44 +7,38 @@ import datadog.trace.instrumentation.netty41.AttributeKeys;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.util.Attribute;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.noop.NoopSpan;
import io.opentracing.util.GlobalTracer;
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
final Attribute<Span> parentAttr =
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY);
parentAttr.setIfAbsent(NoopSpan.INSTANCE);
final Span parent = parentAttr.get();
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get();
if (span == null) {
ctx.fireChannelRead(msg);
return;
final boolean finishSpan = msg instanceof HttpResponse;
if (span != null && finishSpan) {
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
DECORATE.onResponse(span, (HttpResponse) msg);
DECORATE.beforeFinish(span);
span.finish();
}
}
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
final boolean finishSpan = msg instanceof HttpResponse;
// We want the callback in the scope of the parent, not the client span
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
try {
ctx.fireChannelRead(msg);
} catch (final Throwable throwable) {
if (finishSpan) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
Tags.HTTP_STATUS.set(span, 500);
span.finish(); // Finish the span manually since finishSpanOnClose was false
throw throwable;
}
}
if (finishSpan) {
DECORATE.onResponse(span, (HttpResponse) msg);
DECORATE.beforeFinish(span);
span.finish(); // Finish the span manually since finishSpanOnClose was false
}
ctx.fireChannelRead(msg);
}
}
}

View File

@ -9,8 +9,10 @@ import io.netty.channel.ChannelInitializer
import io.netty.channel.embedded.EmbeddedChannel
import io.netty.handler.codec.http.HttpClientCodec
import io.opentracing.tag.Tags
import org.asynchttpclient.AsyncCompletionHandler
import org.asynchttpclient.AsyncHttpClient
import org.asynchttpclient.DefaultAsyncHttpClientConfig
import org.asynchttpclient.Response
import spock.lang.Shared
import java.util.concurrent.ExecutionException
@ -33,8 +35,13 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
def methodName = "prepare" + method.toLowerCase().capitalize()
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
headers.each { requestBuilder.setHeader(it.key, it.value) }
def response = requestBuilder.execute().get()
callback?.call()
def response = requestBuilder.execute(new AsyncCompletionHandler() {
@Override
Object onCompleted(Response response) throws Exception {
callback?.call()
return response
}
}).get()
return response.statusCode
}
@ -75,7 +82,7 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
and:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", thrownException)
basicSpan(it, 0, "parent", null, thrownException)
span(1) {
operationName "netty.connect"

View File

@ -1,7 +1,3 @@
import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
@ -12,8 +8,6 @@ import io.opentracing.Scope
import io.opentracing.Span
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import java.util.concurrent.CountDownLatch
import java.util.regex.Pattern
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
@ -26,21 +20,55 @@ import ratpack.http.client.HttpClient
import ratpack.path.PathBinding
import ratpack.test.exec.ExecHarness
import java.util.concurrent.CountDownLatch
import java.util.regex.Pattern
import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
class RatpackTest extends AgentTestRunner {
OkHttpClient client = OkHttpUtils.client()
def "test path call"() {
def "test bindings for #path"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
context.render("success")
prefix("a") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("b/::\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("c/:val?") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("d/:val") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("e/:val?:\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("f/:val:\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.url(HttpUrl.get(app.address).newBuilder().addPathSegments(path).build())
.get()
.build()
@ -49,12 +77,12 @@ class RatpackTest extends AgentTestRunner {
then:
resp.code() == 200
resp.body.string() == "success"
resp.body.string() == route
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /"
resourceName "GET /$route"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
@ -65,7 +93,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
@ -73,7 +101,7 @@ class RatpackTest extends AgentTestRunner {
}
}
span(1) {
resourceName "GET /"
resourceName "GET /$route"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
@ -84,7 +112,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
defaultTags()
@ -92,85 +120,35 @@ class RatpackTest extends AgentTestRunner {
}
}
}
}
def "test path with bindings call"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
prefix(":foo/:bar?") {
get("baz") {ctx ->
context.render(ctx.get(PathBinding).description)
}
}
}
}
def request = new Request.Builder()
.url(HttpUrl.get(app.address).newBuilder().addPathSegments("a/b/baz").build())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body.string() == ":foo/:bar?/baz"
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /:foo/:bar?/baz"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored false
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${app.address}a/b/baz"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
span(1) {
resourceName "GET /:foo/:bar?/baz"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored false
tags {
"$Tags.COMPONENT.key" "ratpack"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${app.address}a/b/baz"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
where:
path | route
"a" | "a"
"b/123" | "b/::\\d+"
"c" | "c/:val?"
"c/123" | "c/:val?"
"c/foo" | "c/:val?"
"d/123" | "d/:val"
"d/foo" | "d/:val"
"e" | "e/:val?:\\d+"
"e/123" | "e/:val?:\\d+"
"e/foo" | "e/:val?:\\d+"
"f/123" | "f/:val:\\d+"
}
def "test handler error response"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
0 / 0
prefix("handler-error") {
all {
0 / 0
}
}
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.url(app.address.resolve("/handler-error?query=param").toURL())
.get()
.build()
when:
@ -181,7 +159,7 @@ class RatpackTest extends AgentTestRunner {
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /"
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
@ -192,7 +170,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
@ -201,7 +179,7 @@ class RatpackTest extends AgentTestRunner {
}
}
span(1) {
resourceName "GET /"
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
@ -212,7 +190,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
errorTags(HandlerException, Pattern.compile("java.lang.ArithmeticException: Division( is)? undefined"))
@ -227,7 +205,7 @@ class RatpackTest extends AgentTestRunner {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
get("promise-error") {
Promise.async {
0 / 0
}.then {
@ -237,7 +215,7 @@ class RatpackTest extends AgentTestRunner {
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.url(app.address.resolve("promise-error?query=param").toURL())
.get()
.build()
when:
@ -248,7 +226,7 @@ class RatpackTest extends AgentTestRunner {
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /"
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
@ -259,7 +237,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
@ -268,7 +246,7 @@ class RatpackTest extends AgentTestRunner {
}
}
span(1) {
resourceName "GET /"
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
@ -279,7 +257,7 @@ class RatpackTest extends AgentTestRunner {
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$Tags.HTTP_URL.key" "$app.address"
"$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
@ -294,7 +272,7 @@ class RatpackTest extends AgentTestRunner {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
all {
context.render(Promise.sync {
return "fail " + 0 / 0
})
@ -302,7 +280,7 @@ class RatpackTest extends AgentTestRunner {
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.url(app.address.resolve("?query=param").toURL())
.get()
.build()
when:
@ -374,13 +352,13 @@ class RatpackTest extends AgentTestRunner {
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {HttpClient httpClient ->
get { HttpClient httpClient ->
// 1st internal http client call to nested
httpClient.get(HttpUrlBuilder.base(external.address).path("nested").build())
.map {it.body.text}
.flatMap {t ->
.map { it.body.text }
.flatMap { t ->
// make a 2nd http request and concatenate the two bodies together
httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map {t + it.body.text}
httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map { t + it.body.text }
}
.then {
context.render(it)
@ -452,7 +430,7 @@ class RatpackTest extends AgentTestRunner {
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(3))
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
@ -496,9 +474,9 @@ class RatpackTest extends AgentTestRunner {
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {HttpClient httpClient ->
get { HttpClient httpClient ->
httpClient.get(badAddress)
.map {it.body.text}
.map { it.body.text }
.then {
context.render(it)
}
@ -681,7 +659,7 @@ class RatpackTest extends AgentTestRunner {
scope.span().setBaggageItem("test-baggage", "foo")
ParallelBatch.of(testPromise(), testPromise())
.yield()
.map({now ->
.map({ now ->
// close the scope now that we got the baggage inside the promises
scope.close()
return now

View File

@ -429,7 +429,7 @@ class RatpackTest extends AgentTestRunner {
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(3))
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"

View File

@ -0,0 +1,55 @@
package datadog.trace.instrumentation.springwebflux.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.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.util.Map;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public class DefaultWebClientInstrumentation extends Instrumenter.Default {
public DefaultWebClientInstrumentation() {
super("spring-webflux", "spring-webflux-client");
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.HttpClientDecorator",
packageName + ".SpringWebfluxHttpClientDecorator",
packageName + ".HttpHeadersInjectAdapter",
packageName + ".TracingClientResponseSubscriber",
packageName + ".TracingClientResponseSubscriber$1",
packageName + ".TracingClientResponseMono",
};
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return safeHasSuperType(
named("org.springframework.web.reactive.function.client.ExchangeFunction"));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(isPublic())
.and(named("exchange"))
.and(
takesArgument(
0, named("org.springframework.web.reactive.function.client.ClientRequest"))),
packageName + ".DefaultWebClientAdvice");
}
}

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import datadog.trace.agent.tooling.Instrumenter;

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;

View File

@ -0,0 +1,29 @@
package datadog.trace.instrumentation.springwebflux.client;
import io.opentracing.util.GlobalTracer;
import net.bytebuddy.asm.Advice;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import reactor.core.publisher.Mono;
public class DefaultWebClientAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.Thrown final Throwable throwable,
@Advice.This final ExchangeFunction exchangeFunction,
@Advice.Argument(0) final ClientRequest clientRequest,
@Advice.Return(readOnly = false) Mono<ClientResponse> mono) {
if (throwable == null
&& mono != null
// The response of the org.springframework.web.reactive.function.client.ExchangeFunction.exchange method is
// replaced by a decorator that in turn also calls the
// org.springframework.web.reactive.function.client.ExchangeFunction.exchange method. If the original return value
// is already decorated (we detect this if the "x-datadog-trace-id" is added), the result is not decorated again
// to avoid StackOverflowErrors.
&& !clientRequest.headers().keySet().contains("x-datadog-trace-id")) {
mono = new TracingClientResponseMono(clientRequest, exchangeFunction, GlobalTracer.get());
}
}
}

View File

@ -0,0 +1,25 @@
package datadog.trace.instrumentation.springwebflux.client;
import io.opentracing.propagation.TextMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.http.HttpHeaders;
public class HttpHeadersInjectAdapter implements TextMap {
private final HttpHeaders headers;
public HttpHeadersInjectAdapter(final HttpHeaders headers) {
this.headers = headers;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!");
}
@Override
public void put(final String key, final String value) {
headers.set(key, value);
}
}

View File

@ -0,0 +1,55 @@
package datadog.trace.instrumentation.springwebflux.client;
import datadog.trace.agent.decorator.HttpClientDecorator;
import io.opentracing.Span;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
@Slf4j
public class SpringWebfluxHttpClientDecorator
extends HttpClientDecorator<ClientRequest, ClientResponse> {
public static final SpringWebfluxHttpClientDecorator DECORATE =
new SpringWebfluxHttpClientDecorator();
public void onCancel(final Span span) {
span.setTag("event", "cancelled");
span.setTag("message", "The subscription was cancelled");
}
@Override
protected String[] instrumentationNames() {
return new String[] {"spring-webflux", "spring-webflux-client"};
}
@Override
protected String component() {
return "spring-webflux-client";
}
@Override
protected String method(final ClientRequest httpRequest) {
return httpRequest.method().name();
}
@Override
protected URI url(final ClientRequest httpRequest) {
return httpRequest.url();
}
@Override
protected String hostname(final ClientRequest httpRequest) {
return httpRequest.url().getHost();
}
@Override
protected Integer port(final ClientRequest httpRequest) {
return httpRequest.url().getPort();
}
@Override
protected Integer status(final ClientResponse httpResponse) {
return httpResponse.statusCode().value();
}
}

View File

@ -0,0 +1,67 @@
package datadog.trace.instrumentation.springwebflux.client;
import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.tag.Tags;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
public class TracingClientResponseMono extends Mono<ClientResponse> {
private final ClientRequest clientRequest;
private final ExchangeFunction exchangeFunction;
private final Tracer tracer;
public TracingClientResponseMono(
final ClientRequest clientRequest,
final ExchangeFunction exchangeFunction,
final Tracer tracer) {
this.clientRequest = clientRequest;
this.exchangeFunction = exchangeFunction;
this.tracer = tracer;
}
@Override
public void subscribe(final CoreSubscriber<? super ClientResponse> subscriber) {
final Context context = subscriber.currentContext();
final Span parentSpan = context.<Span>getOrEmpty(Span.class).orElseGet(tracer::activeSpan);
final Span span =
tracer
.buildSpan("http.request")
.asChildOf(parentSpan)
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
.start();
DECORATE.afterStart(span);
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
final ClientRequest mutatedRequest =
ClientRequest.from(clientRequest)
.headers(
httpHeaders ->
tracer.inject(
span.context(),
Format.Builtin.HTTP_HEADERS,
new HttpHeadersInjectAdapter(httpHeaders)))
.build();
exchangeFunction
.exchange(mutatedRequest)
.subscribe(
new TracingClientResponseSubscriber(subscriber, mutatedRequest, context, span, parentSpan));
}
}
}

View File

@ -0,0 +1,136 @@
package datadog.trace.instrumentation.springwebflux.client;
import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.noop.NoopSpan;
import io.opentracing.util.GlobalTracer;
import java.util.concurrent.atomic.AtomicReference;
import org.reactivestreams.Subscription;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import reactor.core.CoreSubscriber;
import reactor.util.context.Context;
public class TracingClientResponseSubscriber implements CoreSubscriber<ClientResponse> {
private final Tracer tracer = GlobalTracer.get();
private final CoreSubscriber<? super ClientResponse> subscriber;
private final ClientRequest clientRequest;
private final Context context;
private final AtomicReference<Span> spanRef;
private final Span parentSpan;
public TracingClientResponseSubscriber(
final CoreSubscriber<? super ClientResponse> subscriber,
final ClientRequest clientRequest,
final Context context,
final Span span,
final Span parentSpan) {
this.subscriber = subscriber;
this.clientRequest = clientRequest;
this.context = context;
spanRef = new AtomicReference<>(span);
this.parentSpan = parentSpan == null ? NoopSpan.INSTANCE : parentSpan;
}
@Override
public void onSubscribe(final Subscription subscription) {
final Span span = spanRef.get();
if (span == null) {
subscriber.onSubscribe(subscription);
return;
}
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
DECORATE.onRequest(span, clientRequest);
subscriber.onSubscribe(
new Subscription() {
@Override
public void request(final long n) {
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
subscription.request(n);
}
}
@Override
public void cancel() {
DECORATE.onCancel(span);
DECORATE.beforeFinish(span);
subscription.cancel();
span.finish();
}
});
}
}
@Override
public void onNext(final ClientResponse clientResponse) {
final Span span = spanRef.getAndSet(null);
if (span != null) {
DECORATE.onResponse(span, clientResponse);
DECORATE.beforeFinish(span);
span.finish();
}
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
subscriber.onNext(clientResponse);
}
}
@Override
public void onError(final Throwable throwable) {
final Span span = spanRef.getAndSet(null);
if (span != null) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
span.finish();
}
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
subscriber.onError(throwable);
}
}
@Override
public void onComplete() {
final Span span = spanRef.getAndSet(null);
if (span != null) {
DECORATE.beforeFinish(span);
span.finish();
}
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
subscriber.onComplete();
}
}
@Override
public Context currentContext() {
return context;
}
}

View File

@ -1,10 +1,11 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;
import io.opentracing.Span;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
@ -39,4 +40,10 @@ public class AdviceUtils {
ReactorCoreAdviceUtils.finishSpanIfPresent(
(Span) serverRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
}
public static void finishSpanIfPresent(
final ClientRequest clientRequest, final Throwable throwable) {
ReactorCoreAdviceUtils.finishSpanIfPresent(
(Span) clientRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
}
}

View File

@ -1,6 +1,6 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.context.TraceScope;
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;

View File

@ -1,6 +1,6 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope;

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import datadog.trace.api.DDTags;
import io.opentracing.Span;

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import net.bytebuddy.asm.Advice;
import org.springframework.web.reactive.function.server.HandlerFunction;
@ -14,10 +14,10 @@ public class RouterFunctionAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.This final RouterFunction thiz,
@Advice.Argument(0) final ServerRequest serverRequest,
@Advice.Return(readOnly = false) Mono<HandlerFunction<?>> result,
@Advice.Thrown final Throwable throwable) {
@Advice.This final RouterFunction thiz,
@Advice.Argument(0) final ServerRequest serverRequest,
@Advice.Return(readOnly = false) Mono<HandlerFunction<?>> result,
@Advice.Thrown final Throwable throwable) {
if (throwable == null) {
result = result.doOnSuccessOrError(new RouteOnSuccessOrError(thiz, serverRequest));
} else {

View File

@ -1,4 +1,4 @@
package datadog.trace.instrumentation.springwebflux;
package datadog.trace.instrumentation.springwebflux.server;
import datadog.trace.agent.decorator.ServerDecorator;
import datadog.trace.api.DDSpanTypes;

View File

@ -1,4 +1,4 @@
import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication
import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory

View File

@ -1,10 +1,10 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import dd.trace.instrumentation.springwebflux.EchoHandlerFunction
import dd.trace.instrumentation.springwebflux.FooModel
import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication
import dd.trace.instrumentation.springwebflux.TestController
import dd.trace.instrumentation.springwebflux.server.EchoHandlerFunction
import dd.trace.instrumentation.springwebflux.server.FooModel
import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication
import dd.trace.instrumentation.springwebflux.server.TestController
import io.opentracing.tag.Tags
import okhttp3.OkHttpClient
import okhttp3.Request

View File

@ -0,0 +1,94 @@
package dd.trace.instrumentation.springwebflux.client
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
import datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import org.springframework.http.HttpMethod
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.WebClient
import spock.lang.Shared
class SpringWebfluxHttpClientTest extends HttpClientTest<SpringWebfluxHttpClientDecorator> {
@Shared
def client = WebClient.builder().build()
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def hasParent = GlobalTracer.get().activeSpan() != null
ClientResponse response = client.method(HttpMethod.resolve(method))
.headers { h -> headers.forEach({ key, value -> h.add(key, value) }) }
.uri(uri)
.exchange()
.doOnSuccessOrError { success, error ->
blockUntilChildSpansFinished(1)
callback?.call()
}
.block()
if(hasParent) {
blockUntilChildSpansFinished(callback ? 3 : 2)
}
response.statusCode().value()
}
@Override
SpringWebfluxHttpClientDecorator decorator() {
return SpringWebfluxHttpClientDecorator.DECORATE
}
@Override
// parent spanRef must be cast otherwise it breaks debugging classloading (junit loads it early)
void clientSpan(TraceAssert trace, int index, Object parentSpan, String method = "GET", boolean renameService = false, boolean tagQueryString = false, URI uri = server.address.resolve("/success"), Integer status = 200, Throwable exception = null) {
super.clientSpan(trace, index, parentSpan, method, renameService, tagQueryString, uri, status, exception)
if (!exception) {
trace.span(index + 1) {
childOf(trace.span(index))
serviceName renameService ? "localhost" : "unnamed-java-app"
operationName "netty.client.request"
resourceName "$method $uri.path"
spanType DDSpanTypes.HTTP_CLIENT
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class, exception.message)
}
"$Tags.COMPONENT.key" NettyHttpClientDecorator.DECORATE.component()
if (status) {
"$Tags.HTTP_STATUS.key" status
}
"$Tags.HTTP_URL.key" "${uri.resolve(uri.path)}"
if (tagQueryString) {
"$DDTags.HTTP_QUERY" uri.query
"$DDTags.HTTP_FRAGMENT" { it == null || it == uri.fragment } // Optional
}
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" uri.port
"$Tags.PEER_HOST_IPV4.key" { it == null || it == "127.0.0.1" } // Optional
"$Tags.HTTP_METHOD.key" method
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
}
}
}
}
@Override
int size(int size) {
return size + 1
}
boolean testRedirects() {
false
}
boolean testConnectionFailure() {
false
}
}

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux
package dd.trace.instrumentation.springwebflux.server
import datadog.trace.api.Trace
import org.springframework.http.MediaType

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux
package dd.trace.instrumentation.springwebflux.server
import org.springframework.web.reactive.function.server.HandlerFunction

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux
package dd.trace.instrumentation.springwebflux.server
class FooModel {
public long id

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux
package dd.trace.instrumentation.springwebflux.server
import datadog.trace.api.Trace
import org.springframework.boot.autoconfigure.SpringBootApplication

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux
package dd.trace.instrumentation.springwebflux.server
import datadog.trace.api.Trace
import org.springframework.web.bind.annotation.GetMapping

View File

@ -1,4 +1,4 @@
package dd.trace.instrumentation.springwebflux;
package dd.trace.instrumentation.springwebflux.server;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

View File

@ -2,8 +2,10 @@ package datadog.trace.instrumentation.trace_annotation;
import static datadog.trace.instrumentation.trace_annotation.TraceDecorator.DECORATE;
import datadog.trace.api.DDTags;
import datadog.trace.api.Trace;
import io.opentracing.Scope;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;
@ -12,12 +14,20 @@ public class TraceAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope startSpan(@Advice.Origin final Method method) {
final Trace trace = method.getAnnotation(Trace.class);
String operationName = trace == null ? null : trace.operationName();
final Trace traceAnnotation = method.getAnnotation(Trace.class);
String operationName = traceAnnotation == null ? null : traceAnnotation.operationName();
if (operationName == null || operationName.isEmpty()) {
operationName = DECORATE.spanNameForMethod(method);
}
return DECORATE.afterStart(GlobalTracer.get().buildSpan(operationName).startActive(true));
Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(operationName);
final String resourceName = traceAnnotation == null ? null : traceAnnotation.resourceName();
if (resourceName != null && !resourceName.isEmpty()) {
spanBuilder = spanBuilder.withTag(DDTags.RESOURCE_NAME, resourceName);
}
return DECORATE.afterStart(spanBuilder.startActive(true));
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)

View File

@ -38,6 +38,77 @@ class TraceAnnotationsTest extends AgentTestRunner {
}
}
def "test simple case with only operation name set"() {
setup:
// Test single span in new trace
SayTracedHello.sayHA()
expect:
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "test"
resourceName "SAY_HA"
operationName "SAY_HA"
spanType "DB"
parent()
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
}
}
}
def "test simple case with only resource name set"() {
setup:
// Test single span in new trace
SayTracedHello.sayHelloOnlyResourceSet()
expect:
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "test"
resourceName "WORLD"
operationName "SayTracedHello.sayHelloOnlyResourceSet"
parent()
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
}
}
}
def "test simple case with both resource and operation name set"() {
setup:
// Test single span in new trace
SayTracedHello.sayHAWithResource()
expect:
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "test"
resourceName "EARTH"
operationName "SAY_HA"
spanType "DB"
parent()
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
}
}
}
def "test complex case annotations"() {
when:
// Test new trace with 2 children spans
@ -82,6 +153,94 @@ class TraceAnnotationsTest extends AgentTestRunner {
}
}
def "test complex case with resource name at top level"() {
when:
// Test new trace with 2 children spans
SayTracedHello.sayHELLOsayHAWithResource()
then:
assertTraces(1) {
trace(0, 3) {
span(0) {
resourceName "WORLD"
operationName "NEW_TRACE"
parent()
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
span(1) {
resourceName "SAY_HA"
operationName "SAY_HA"
spanType "DB"
childOf span(0)
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
span(2) {
serviceName "test"
resourceName "SayTracedHello.sayHello"
operationName "SayTracedHello.sayHello"
childOf span(0)
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
}
}
}
def "test complex case with resource name at various levels"() {
when:
// Test new trace with 2 children spans
SayTracedHello.sayHELLOsayHAMixedResourceChildren()
then:
assertTraces(1) {
trace(0, 3) {
span(0) {
resourceName "WORLD"
operationName "NEW_TRACE"
parent()
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
span(1) {
resourceName "EARTH"
operationName "SAY_HA"
spanType "DB"
childOf span(0)
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
span(2) {
serviceName "test"
resourceName "SayTracedHello.sayHello"
operationName "SayTracedHello.sayHello"
childOf span(0)
errored false
tags {
"$Tags.COMPONENT.key" "trace"
defaultTags()
}
}
}
}
}
def "test exception exit"() {
setup:
@ -111,6 +270,35 @@ class TraceAnnotationsTest extends AgentTestRunner {
}
}
def "test exception exit with resource name"() {
setup:
TEST_TRACER.addDecorator(new ErrorFlag())
Throwable error = null
try {
SayTracedHello.sayERRORWithResource()
} catch (final Throwable ex) {
error = ex
}
expect:
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "WORLD"
operationName "ERROR"
errored true
tags {
"$Tags.COMPONENT.key" "trace"
errorTags(error.class)
defaultTags()
}
}
}
}
}
def "test annonymous class annotations"() {
setup:
// Test anonymous classes with package.

View File

@ -15,6 +15,13 @@ public class SayTracedHello {
return "hello!";
}
@Trace(resourceName = "WORLD")
public static String sayHelloOnlyResourceSet() {
new StringTag(DDTags.SERVICE_NAME)
.set(GlobalTracer.get().scopeManager().active().span(), "test");
return "hello!";
}
@Trace(operationName = "SAY_HA")
public static String sayHA() {
new StringTag(DDTags.SERVICE_NAME)
@ -23,6 +30,14 @@ public class SayTracedHello {
return "HA!!";
}
@Trace(operationName = "SAY_HA", resourceName = "EARTH")
public static String sayHAWithResource() {
new StringTag(DDTags.SERVICE_NAME)
.set(GlobalTracer.get().scopeManager().active().span(), "test");
new StringTag(DDTags.SPAN_TYPE).set(GlobalTracer.get().scopeManager().active().span(), "DB");
return "HA EARTH!!";
}
@Trace(operationName = "NEW_TRACE")
public static String sayHELLOsayHA() {
new StringTag(DDTags.SERVICE_NAME)
@ -30,11 +45,30 @@ public class SayTracedHello {
return sayHello() + sayHA();
}
@Trace(operationName = "NEW_TRACE", resourceName = "WORLD")
public static String sayHELLOsayHAWithResource() {
new StringTag(DDTags.SERVICE_NAME)
.set(GlobalTracer.get().scopeManager().active().span(), "test2");
return sayHello() + sayHA();
}
@Trace(operationName = "NEW_TRACE", resourceName = "WORLD")
public static String sayHELLOsayHAMixedResourceChildren() {
new StringTag(DDTags.SERVICE_NAME)
.set(GlobalTracer.get().scopeManager().active().span(), "test2");
return sayHello() + sayHAWithResource();
}
@Trace(operationName = "ERROR")
public static String sayERROR() {
throw new RuntimeException();
}
@Trace(operationName = "ERROR", resourceName = "WORLD")
public static String sayERRORWithResource() {
throw new RuntimeException();
}
public static String fromCallable() throws Exception {
return new Callable<String>() {
@com.newrelic.api.agent.Trace

View File

@ -1,151 +1,54 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
import io.netty.channel.AbstractChannel
import io.opentracing.tag.Tags
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
import io.vertx.core.Vertx
import io.vertx.core.VertxOptions
import io.vertx.core.http.HttpClient
import io.vertx.core.http.HttpClientRequest
import io.vertx.core.http.HttpClientResponse
import io.vertx.core.http.HttpMethod
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Timeout
import java.util.concurrent.CompletableFuture
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
class VertxHttpClientTest extends AgentTestRunner {
private static final String MESSAGE = "hello world"
@AutoCleanup
@Shared
def server = httpServer {
handlers {
prefix("success") {
handleDistributedRequest()
response.status(200).send(MESSAGE)
}
prefix("error") {
handleDistributedRequest()
throw new RuntimeException("error")
}
}
}
@Timeout(10)
class VertxHttpClientTest extends HttpClientTest<NettyHttpClientDecorator> {
@Shared
Vertx vertx = Vertx.vertx(new VertxOptions())
@Shared
HttpClient httpClient = vertx.createHttpClient()
def "#route request trace"() {
setup:
def responseFuture = new CompletableFuture<HttpClientResponse>()
def messageFuture = new CompletableFuture<String>()
httpClient.getNow(server.address.port, server.address.host, "/" + route, { response ->
responseFuture.complete(response)
response.bodyHandler({ buffer ->
messageFuture.complete(buffer.toString())
})
})
when:
HttpClientResponse response = responseFuture.get()
String message = messageFuture.get()
then:
response.statusCode() == expectedStatus
if (expectedMessage != null) {
message == expectedMessage
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
CompletableFuture<HttpClientResponse> future = new CompletableFuture<>()
def request = httpClient.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
headers.each { request.putHeader(it.key, it.value) }
request.handler { response ->
callback?.call()
future.complete(response)
}
request.end()
assertTraces(2) {
server.distributedRequestTrace(it, 0, TEST_WRITER[1][0])
trace(1, 1) {
span(0) {
parent()
serviceName "unnamed-java-app"
operationName "netty.client.request"
resourceName "GET /$route"
spanType DDSpanTypes.HTTP_CLIENT
errored expectedError
tags {
defaultTags()
"$Tags.HTTP_STATUS.key" expectedStatus
"$Tags.HTTP_URL.key" "${server.address}/$route"
"$Tags.PEER_HOSTNAME.key" server.address.host
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.COMPONENT.key" "netty-client"
if (expectedError) {
"$Tags.ERROR.key" true
}
}
}
}
}
where:
route | expectedStatus | expectedError | expectedMessage
"success" | 200 | false | MESSAGE
"error" | 500 | true | null
return future.get().statusCode()
}
def "test connection failure"() {
setup:
def invalidPort = PortUtils.randomOpenPort()
@Override
NettyHttpClientDecorator decorator() {
return NettyHttpClientDecorator.DECORATE
}
def errorFuture = new CompletableFuture<Throwable>()
@Override
String expectedOperationName() {
return "netty.client.request"
}
runUnderTrace("parent") {
HttpClientRequest request = httpClient.request(
HttpMethod.GET,
invalidPort,
server.address.host,
"/",
{ response ->
// We expect to never get here since our request is expected to fail
errorFuture.complete(null)
})
request.exceptionHandler({ error ->
errorFuture.complete(error)
})
request.end()
}
@Override
boolean testRedirects() {
false
}
when:
def throwable = errorFuture.get()
then:
throwable.cause instanceof ConnectException
and:
assertTraces(1) {
trace(0, 2) {
span(0) {
operationName "parent"
parent()
}
span(1) {
operationName "netty.connect"
resourceName "netty.connect"
childOf span(0)
errored true
tags {
"$Tags.COMPONENT.key" "netty"
errorTags AbstractChannel.AnnotatedConnectException, "Connection refused: localhost/127.0.0.1:$invalidPort"
defaultTags()
}
}
}
}
@Override
boolean testConnectionFailure() {
false
}
}

View File

@ -0,0 +1,157 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
import io.netty.handler.codec.http.HttpResponseStatus
import io.opentracing.tag.Tags
import io.vertx.core.Vertx
import okhttp3.OkHttpClient
import okhttp3.Request
import spock.lang.Shared
class VertxRxServerTest extends AgentTestRunner {
@Shared
OkHttpClient client = OkHttpUtils.client()
@Shared
int port
@Shared
Vertx server
def setupSpec() {
port = PortUtils.randomOpenPort()
server = VertxRxWebTestServer.start(port)
}
def cleanupSpec() {
server.close()
}
def "test server request/response"() {
setup:
def request = new Request.Builder()
.url("http://localhost:$port/proxy")
.header("x-datadog-trace-id", "123")
.header("x-datadog-parent-id", "456")
.get()
.build()
def response = client.newCall(request).execute()
expect:
response.code() == 200
response.body().string() == "Hello World"
and:
assertTraces(2) {
trace(0, 2) {
span(0) {
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName "GET /test"
childOf(trace(1).get(1))
spanType DDSpanTypes.HTTP_SERVER
errored false
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
defaultTags(true)
}
}
span(1) {
childOf span(0)
assert span(1).operationName.endsWith('.tracedMethod')
}
}
trace(1, 2) {
span(0) {
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName "GET /proxy"
traceId "123"
parentId "456"
spanType DDSpanTypes.HTTP_SERVER
errored false
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
defaultTags(true)
}
}
span(1) {
serviceName "unnamed-java-app"
operationName "netty.client.request"
resourceName "GET /test"
childOf(span(0))
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
defaultTags()
}
}
}
}
}
def "test #responseCode response handling"() {
setup:
def request = new Request.Builder().url("http://localhost:$port/$path").get().build()
def response = client.newCall(request).execute()
expect:
response.code() == responseCode.code()
and:
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName name
spanType DDSpanTypes.HTTP_SERVER
errored error
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" responseCode.code()
"$Tags.HTTP_URL.key" "http://localhost:$port/$path"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
if (error) {
tag("error", true)
}
defaultTags()
}
}
}
}
where:
responseCode | name | path | error
HttpResponseStatus.OK | "GET /" | "" | false
HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false
HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true
}
}

View File

@ -0,0 +1,49 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
import io.vertx.core.VertxOptions
import io.vertx.core.http.HttpMethod
import io.vertx.reactivex.core.Vertx
import io.vertx.reactivex.ext.web.client.WebClient
import spock.lang.Shared
import spock.lang.Timeout
@Timeout(10)
class VertxRxWebClientTest extends HttpClientTest<NettyHttpClientDecorator> {
@Shared
Vertx vertx = Vertx.vertx(new VertxOptions())
@Shared
WebClient client = WebClient.create(vertx)
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
headers.each { request.putHeader(it.key, it.value) }
return request
.rxSend()
.doOnSuccess { response -> callback?.call() }
.map { it.statusCode() }
.toObservable()
.blockingFirst()
}
@Override
NettyHttpClientDecorator decorator() {
return NettyHttpClientDecorator.DECORATE
}
@Override
String expectedOperationName() {
return "netty.client.request"
}
@Override
boolean testRedirects() {
false
}
@Override
boolean testConnectionFailure() {
false
}
}

View File

@ -31,7 +31,7 @@ class VertxServerTest extends AgentTestRunner {
def "test server request/response"() {
setup:
def request = new Request.Builder()
.url("http://localhost:$port/test")
.url("http://localhost:$port/proxy")
.header("x-datadog-trace-id", "123")
.header("x-datadog-parent-id", "456")
.get()
@ -43,14 +43,13 @@ class VertxServerTest extends AgentTestRunner {
response.body().string() == "Hello World"
and:
assertTraces(1) {
assertTraces(2) {
trace(0, 2) {
span(0) {
traceId "123"
parentId "456"
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName "GET /test"
childOf(trace(1).get(1))
spanType DDSpanTypes.HTTP_SERVER
errored false
tags {
@ -70,6 +69,47 @@ class VertxServerTest extends AgentTestRunner {
assert span(1).operationName.endsWith('.tracedMethod')
}
}
trace(1, 2) {
span(0) {
serviceName "unnamed-java-app"
operationName "netty.request"
resourceName "GET /proxy"
traceId "123"
parentId "456"
spanType DDSpanTypes.HTTP_SERVER
errored false
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
defaultTags(true)
}
}
span(1) {
serviceName "unnamed-java-app"
operationName "netty.client.request"
resourceName "GET /test"
childOf(span(0))
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
defaultTags()
}
}
}
}
}

View File

@ -0,0 +1,116 @@
import datadog.trace.api.Trace;
import io.vertx.circuitbreaker.CircuitBreakerOptions;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.reactivex.circuitbreaker.CircuitBreaker;
import io.vertx.reactivex.core.AbstractVerticle;
import io.vertx.reactivex.core.buffer.Buffer;
import io.vertx.reactivex.ext.web.Router;
import io.vertx.reactivex.ext.web.RoutingContext;
import io.vertx.reactivex.ext.web.client.WebClient;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class VertxRxWebTestServer extends AbstractVerticle {
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
/* This is highly against Vertx ideas, but our tests are synchronous
so we have to make sure server is up and running */
final CompletableFuture<Void> future = new CompletableFuture<>();
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
vertx.deployVerticle(
VertxRxWebTestServer.class.getName(),
new DeploymentOptions()
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
.setInstances(3),
res -> {
if (!res.succeeded()) {
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
}
future.complete(null);
});
future.get();
return vertx;
}
@Override
public void start(final Future<Void> startFuture) {
// final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx);
final WebClient client = WebClient.create(vertx);
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
final Router router = Router.router(vertx);
final CircuitBreaker breaker =
CircuitBreaker.create(
"my-circuit-breaker",
vertx,
new CircuitBreakerOptions()
.setMaxFailures(5) // number of failure before opening the circuit
.setTimeout(2000) // consider a failure if the operation does not succeed in time
// .setFallbackOnFailure(true) // do we call the fallback on failure
.setResetTimeout(10000) // time spent in open state before attempting to re-try
);
router
.route("/")
.handler(
routingContext -> {
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
});
router
.route("/error")
.handler(
routingContext -> {
routingContext.response().setStatusCode(500).end();
});
router
.route("/proxy")
.handler(
routingContext -> {
breaker.execute(
ctx -> {
client
.get(port, "localhost", "/test")
.rxSendBuffer(
Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()))
.subscribe(
response -> {
routingContext
.response()
.setStatusCode(response.statusCode())
.end(response.body());
});
});
});
router
.route("/test")
.handler(
routingContext -> {
tracedMethod();
routingContext.next();
})
.blockingHandler(RoutingContext::next)
.handler(
routingContext -> {
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
});
vertx
.createHttpServer()
.requestHandler(router::accept)
.listen(port, h -> startFuture.complete());
}
@Trace
private void tracedMethod() {}
}

View File

@ -1,25 +1,36 @@
import datadog.trace.api.Trace;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class VertxWebTestServer extends AbstractVerticle {
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
/* This is highly against Vertx ideas, but our tests are synchronous
so we have to make sure server is up and running */
final CompletableFuture<Void> future = new CompletableFuture<>();
final Vertx vertx = Vertx.vertx(new VertxOptions());
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
vertx.deployVerticle(
new VertxWebTestServer(port),
VertxWebTestServer.class.getName(),
new DeploymentOptions()
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
.setInstances(3),
res -> {
if (!res.succeeded()) {
throw new RuntimeException("Cannot deploy server Verticle");
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
}
future.complete(null);
});
@ -29,14 +40,12 @@ public class VertxWebTestServer extends AbstractVerticle {
return vertx;
}
private final int port;
public VertxWebTestServer(final int port) {
this.port = port;
}
@Override
public void start(final Future<Void> startFuture) {
final HttpClient client = vertx.createHttpClient();
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
final Router router = Router.router(vertx);
router
@ -51,6 +60,26 @@ public class VertxWebTestServer extends AbstractVerticle {
routingContext -> {
routingContext.response().setStatusCode(500).end();
});
router
.route("/proxy")
.handler(
routingContext -> {
client
.get(
port,
"localhost",
"/test",
response -> {
response.bodyHandler(
buffer -> {
routingContext
.response()
.setStatusCode(response.statusCode())
.end(buffer);
});
})
.end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()));
});
router
.route("/test")
.handler(
@ -58,10 +87,7 @@ public class VertxWebTestServer extends AbstractVerticle {
tracedMethod();
routingContext.next();
})
.blockingHandler(
routingContext -> {
routingContext.next();
})
.blockingHandler(RoutingContext::next)
.handler(
routingContext -> {
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
@ -74,5 +100,5 @@ public class VertxWebTestServer extends AbstractVerticle {
}
@Trace
public void tracedMethod() {}
private void tracedMethod() {}
}

View File

@ -42,10 +42,19 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile project(':dd-java-agent:instrumentation:trace-annotation')
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0'
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+'
// Tests seem to fail before 3.5... maybe a problem with some of the tests?
testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0'
testCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.5.0'
testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0'
testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0'
// Vert.x 4.0 is incompatible with our tests.
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '3.+'
latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.+'
latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.+'
latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.+'
}

View File

@ -18,6 +18,7 @@ import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import groovy.transform.stc.ClosureParams;
import groovy.transform.stc.SimpleType;
import io.opentracing.Span;
import io.opentracing.Tracer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
@ -202,15 +203,13 @@ public abstract class AgentTestRunner extends Specification {
}
public void blockUntilChildSpansFinished(final int numberOfSpans) throws InterruptedException {
final DDSpan span = (DDSpan) io.opentracing.util.GlobalTracer.get().activeSpan();
if (span == null) {
// If there is no active span avoid getting an NPE
return;
}
final PendingTrace pendingTrace = span.context().getTrace();
final Span span = io.opentracing.util.GlobalTracer.get().activeSpan();
if (span instanceof DDSpan) {
final PendingTrace pendingTrace = ((DDSpan) span).context().getTrace();
while (pendingTrace.size() < numberOfSpans) {
Thread.sleep(10);
while (pendingTrace.size() < numberOfSpans) {
Thread.sleep(10);
}
}
}

View File

@ -176,10 +176,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
assertTraces(1) {
trace(0, size(3)) {
basicSpan(it, 0, "parent")
span(1) {
operationName "child"
childOf span(0)
}
basicSpan(it, 1, "child", span(0))
clientSpan(it, 2, span(0), method, false)
}
}
@ -191,7 +188,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
def "trace request with callback and no parent"() {
when:
def status = doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"]) {
runUnderTrace("child") {
runUnderTrace("callback") {
// Ensure consistent ordering of traces for assertion.
TEST_WRITER.waitForTraces(1)
}
@ -205,10 +202,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
clientSpan(it, 0, null, method, false)
}
trace(1, 1) {
span(0) {
operationName "child"
parent()
}
basicSpan(it, 0, "callback")
}
}
@ -306,7 +300,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
and:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", thrownException)
basicSpan(it, 0, "parent", null, thrownException)
clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException)
}
}

View File

@ -1,5 +1,6 @@
package datadog.trace.agent.test.utils
import datadog.opentracing.DDSpan
import datadog.trace.agent.decorator.BaseDecorator
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.context.TraceScope
@ -42,9 +43,13 @@ class TraceUtils {
}
}
static basicSpan(TraceAssert trace, int index, String spanName, Throwable exception = null) {
static basicSpan(TraceAssert trace, int index, String spanName, Object parentSpan = null, Throwable exception = null) {
trace.span(index) {
parent()
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
serviceName "unnamed-java-app"
operationName spanName
resourceName spanName

View File

@ -1,8 +1,8 @@
# Datadog Smoke Tests
Assert that various application servers will start up with the Datadog JavaAgent without any obvious ill effects.
Assert that various applications will start up with the Datadog JavaAgent without any obvious ill effects.
Each subproject underneath `dd-smoke-tests` is a single smoke test. Each test does the following
* Launch the app server with stdout and stderr logged to `$buildDir/reports/server.log`
* Run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response.
* Launch the application with stdout and stderr logged to `$buildDir/reports/server.log`
* For web servers, run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response.
Note that there is nothing special about doing 200 requests. 200 is simply an arbitrarily large number to exercise the server.

View File

@ -0,0 +1,25 @@
plugins {
id "com.github.johnrengelman.shadow" version "4.0.4"
}
apply from: "${rootDir}/gradle/java.gradle"
description = 'Command Line Application Smoke Tests.'
jar {
manifest {
attributes(
'Main-Class': 'datadog.smoketest.cli.CliApplication'
)
}
}
dependencies {
compile project(':dd-trace-api')
testCompile project(':dd-smoke-tests')
}
tasks.withType(Test).configureEach {
dependsOn shadowJar
jvmArgs "-Ddatadog.smoketest.cli.shadowJar.path=${tasks.shadowJar.archivePath}"
}

View File

@ -0,0 +1,25 @@
package datadog.smoketest.cli;
import datadog.trace.api.Trace;
/** Simple application that sleeps then quits. */
public class CliApplication {
public static void main(final String[] args) throws InterruptedException {
final CliApplication app = new CliApplication();
// Sleep to ensure all of the processes are running
Thread.sleep(5000);
System.out.println("Calling example trace");
app.exampleTrace();
System.out.println("Finished calling example trace");
}
@Trace(operationName = "example")
public void exampleTrace() throws InterruptedException {
Thread.sleep(500);
}
}

View File

@ -0,0 +1,28 @@
package datadog.smoketest
import spock.lang.Timeout
import java.util.concurrent.TimeUnit
class CliApplicationSmokeTest extends AbstractSmokeTest {
// Estimate for the amount of time instrumentation, plus request, plus some extra
private static final int TIMEOUT_SECS = 15
@Override
ProcessBuilder createProcessBuilder() {
String cliShadowJar = System.getProperty("datadog.smoketest.cli.shadowJar.path")
List<String> command = new ArrayList<>()
command.add(javaPath())
command.addAll(defaultJavaProperties)
command.addAll((String[]) ["-jar", cliShadowJar])
ProcessBuilder processBuilder = new ProcessBuilder(command)
processBuilder.directory(new File(buildDirectory))
}
@Timeout(value = TIMEOUT_SECS, unit = TimeUnit.SECONDS)
def "Cli application process ends before timeout"() {
expect:
assert serverProcess.waitFor() == 0
}
}

View File

@ -1,9 +1,11 @@
package datadog.trace.api;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
@ -885,7 +887,7 @@ public class Config {
* exist or if it is in a wrong format.
*/
private static Properties loadConfigurationFile() {
Properties properties = new Properties();
final Properties properties = new Properties();
// Reading from system property first and from env after
String configurationFilePath =
@ -922,11 +924,38 @@ public class Config {
return properties;
}
/**
* Returns the detected hostname. This operation is time consuming so if the usage changes and
* this method will be called several times then we should implement some sort of caching.
*/
/** Returns the detected hostname. First tries locally, then using DNS */
private String getHostName() {
String possibleHostname = null;
// Try environment variable. This works in almost all environments
if (System.getProperty("os.name").startsWith("Windows")) {
possibleHostname = System.getenv("COMPUTERNAME");
} else {
possibleHostname = System.getenv("HOSTNAME");
}
if (possibleHostname != null && !possibleHostname.isEmpty()) {
log.debug("Determined hostname from environment variable");
return possibleHostname.trim();
}
// Try hostname command
try {
final Process process = Runtime.getRuntime().exec("hostname");
final BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
possibleHostname = reader.readLine();
} catch (final Exception e) {
// Ignore. Hostname command is not always available
}
if (possibleHostname != null && !possibleHostname.isEmpty()) {
log.debug("Determined hostname from hostname command");
return possibleHostname.trim();
}
// From DNS
try {
return InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException e) {

View File

@ -13,4 +13,7 @@ public @interface Trace {
/** The operation name to set. By default it takes the method's name */
String operationName() default "";
/** The resource name. By default it uses the same value as the operation name */
String resourceName() default "";
}

View File

@ -724,7 +724,7 @@ class ConfigTest extends Specification {
def config = Config.get(properties)
then:
config.localRootSpanTags.get('_dd.hostname') == InetAddress.localHost.hostName
config.localRootSpanTags.containsKey('_dd.hostname')
}
def "verify fallback to properties file"() {

View File

@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null
allprojects {
group = 'com.datadoghq'
version = '0.31.0-SNAPSHOT'
version = '0.32.0-SNAPSHOT'
if (isCI) {
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"
@ -52,7 +52,6 @@ buildScan {
wrapper {
distributionType = Wrapper.DistributionType.ALL
version = '5.5.1'
}
allprojects {

View File

@ -0,0 +1,128 @@
package datadog.opentracing;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* Parses container information from /proc/self/cgroup. Implementation based largely on
* Qard/container-info
*/
@Getter
@Setter
@Slf4j
public class ContainerInfo {
private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup");
private static final String UUID_REGEX =
"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}";
private static final String CONTAINER_REGEX = "[0-9a-f]{64}";
private static final Pattern LINE_PATTERN = Pattern.compile("(\\d+):([^:]*):(.+)$");
private static final Pattern POD_PATTERN =
Pattern.compile("(?:.+)?pod(" + UUID_REGEX + ")(?:.slice)?$");
private static final Pattern CONTAINER_PATTERN =
Pattern.compile("(?:.+)?(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$");
private static final ContainerInfo INSTANCE;
public String containerId;
public String podId;
public List<CGroupInfo> cGroups = new ArrayList<>();
static {
ContainerInfo containerInfo = new ContainerInfo();
if (ContainerInfo.isRunningInContainer()) {
try {
containerInfo = ContainerInfo.fromDefaultProcFile();
} catch (final IOException | ParseException e) {
log.error("Unable to parse proc file");
}
}
INSTANCE = containerInfo;
}
@Getter
@Setter
public static class CGroupInfo {
public int id;
public String path;
public List<String> controllers;
public String containerId;
public String podId;
}
public static ContainerInfo get() {
return INSTANCE;
}
public static boolean isRunningInContainer() {
return Files.isReadable(CGROUP_DEFAULT_PROCFILE);
}
public static ContainerInfo fromDefaultProcFile() throws IOException, ParseException {
final String content = new String(Files.readAllBytes(CGROUP_DEFAULT_PROCFILE));
return parse(content);
}
public static ContainerInfo parse(final String cgroupsContent) throws ParseException {
final ContainerInfo containerInfo = new ContainerInfo();
final String[] lines = cgroupsContent.split("\n");
for (final String line : lines) {
final CGroupInfo cGroupInfo = parseLine(line);
containerInfo.getCGroups().add(cGroupInfo);
if (cGroupInfo.getPodId() != null) {
containerInfo.setPodId(cGroupInfo.getPodId());
}
if (cGroupInfo.getContainerId() != null) {
containerInfo.setContainerId(cGroupInfo.getContainerId());
}
}
return containerInfo;
}
static CGroupInfo parseLine(final String line) throws ParseException {
final Matcher matcher = LINE_PATTERN.matcher(line);
if (!matcher.matches()) {
throw new ParseException("Unable to match cgroup", 0);
}
final CGroupInfo cGroupInfo = new CGroupInfo();
cGroupInfo.setId(Integer.parseInt(matcher.group(1)));
cGroupInfo.setControllers(Arrays.asList(matcher.group(2).split(",")));
final String path = matcher.group(3);
final String[] pathParts = path.split("/");
cGroupInfo.setPath(path);
if (pathParts.length >= 1) {
final Matcher containerIdMatcher = CONTAINER_PATTERN.matcher(pathParts[pathParts.length - 1]);
final String containerId = containerIdMatcher.matches() ? containerIdMatcher.group(1) : null;
cGroupInfo.setContainerId(containerId);
}
if (pathParts.length >= 2) {
final Matcher podIdMatcher = POD_PATTERN.matcher(pathParts[pathParts.length - 2]);
final String podId = podIdMatcher.matches() ? podIdMatcher.group(1) : null;
cGroupInfo.setPodId(podId);
}
return cGroupInfo;
}
}

View File

@ -44,6 +44,7 @@ public class ContinuableScope implements Scope, TraceScope {
final Continuation continuation,
final DDSpan spanUnderScope,
final boolean finishOnClose) {
assert spanUnderScope != null : "span must not be null";
this.scopeManager = scopeManager;
this.openCount = openCount;
this.continuation = continuation;

View File

@ -15,10 +15,11 @@ public class SimpleScope implements Scope {
final ContextualScopeManager scopeManager,
final Span spanUnderScope,
final boolean finishOnClose) {
assert spanUnderScope != null : "span must not be null";
this.scopeManager = scopeManager;
this.spanUnderScope = spanUnderScope;
this.finishOnClose = finishOnClose;
this.toRestore = scopeManager.tlsScope.get();
toRestore = scopeManager.tlsScope.get();
scopeManager.tlsScope.set(this);
for (final ScopeListener listener : scopeManager.scopeListeners) {
listener.afterScopeActivated();

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import datadog.opentracing.ContainerInfo;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDTraceOTInfo;
import datadog.trace.common.writer.unixdomainsockets.UnixDomainSocketFactory;
@ -35,6 +36,7 @@ public class DDApi {
private static final String DATADOG_META_LANG_INTERPRETER_VENDOR =
"Datadog-Meta-Lang-Interpreter-Vendor";
private static final String DATADOG_META_TRACER_VERSION = "Datadog-Meta-Tracer-Version";
private static final String DATADOG_CONTAINER_ID = "Datadog-Container-ID";
private static final String X_DATADOG_TRACE_COUNT = "X-Datadog-Trace-Count";
private static final int HTTP_TIMEOUT = 1; // 1 second for conenct/read/write operations
@ -261,13 +263,21 @@ public class DDApi {
}
private static Request.Builder prepareRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)
.addHeader(DATADOG_META_LANG, "java")
.addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION)
.addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME)
.addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR)
.addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION);
final Request.Builder builder =
new Request.Builder()
.url(url)
.addHeader(DATADOG_META_LANG, "java")
.addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION)
.addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME)
.addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR)
.addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION);
final String containerId = ContainerInfo.get().getContainerId();
if (containerId == null) {
return builder;
} else {
return builder.addHeader(DATADOG_CONTAINER_ID, containerId);
}
}
@Override

View File

@ -0,0 +1,148 @@
package datadog.opentracing
import spock.lang.Specification
import spock.lang.Unroll
class ContainerInfoTest extends Specification {
@Unroll
def "CGroupInfo is parsed from individual lines"() {
when:
ContainerInfo.CGroupInfo cGroupInfo = ContainerInfo.parseLine(line)
then:
cGroupInfo.getId() == id
cGroupInfo.getPath() == path
cGroupInfo.getControllers() == controllers
cGroupInfo.getContainerId() == containerId
cGroupInfo.podId == podId
// Examples from container tagging rfc and Qard/container-info
where:
id | controllers | path | containerId | podId | line
// Docker examples
13 | ["name=systemd"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
12 | ["pids"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
11 | ["hugetlb"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
10 | ["net_prio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
9 | ["perf_event"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
8 | ["net_cls"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
7 | ["freezer"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
6 | ["devices"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
5 | ["memory"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
4 | ["blkio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
3 | ["cpuacct"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
2 | ["cpu"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
1 | ["cpuset"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
// Kubernates examples
11 | ["perf_event"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
10 | ["pids"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
9 | ["memory"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
8 | ["cpu", "cpuacct"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
7 | ["blkio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
6 | ["cpuset"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
5 | ["devices"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
4 | ["freezer"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
3 | ["net_cls", "net_prio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
2 | ["hugetlb"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
1 | ["name=systemd"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
//ECS examples
9 | ["perf_event"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
8 | ["memory"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
7 | ["hugetlb"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
6 | ["freezer"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
5 | ["devices"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
4 | ["cpuset"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
3 | ["cpuacct"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
2 | ["cpu"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
1 | ["blkio"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
//Fargate Examples
11 | ["hugetlb"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
10 | ["pids"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
9 | ["cpuset"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
8 | ["net_cls", "net_prio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
7 | ["cpu", "cpuacct"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
6 | ["perf_event"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
5 | ["freezer"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
4 | ["devices"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
3 | ["blkio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
2 | ["memory"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
1 | ["name=systemd"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
//Reference impl examples
1 | ["name=systemd"] | "/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" | "cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411" | null | "1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope"
1 | ["name=systemd"] | "/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" | null | null | "1:name=systemd:/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex"
1 | ["name=systemd"] | "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" | "2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63" | "90d81341_92de_11e7_8cf2_507b9d4141fa" | "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope"
}
@Unroll
def "Container info parsed from file content"() {
when:
ContainerInfo containerInfo = ContainerInfo.parse(content)
then:
containerInfo.getContainerId() == containerId
containerInfo.getPodId() == podId
containerInfo.getCGroups().size() == size
where:
containerId | podId | size | content
// Docker
"3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | 13 | """13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"""
// Kubernetes
"3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | 11 | """11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"""
// ECS
"38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | 9 | """9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"""
// Fargate
"432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | 11 | """11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"""
}
}

View File

@ -6,6 +6,8 @@ import datadog.trace.api.Config
import datadog.trace.api.DDTags
import datadog.trace.api.sampling.PrioritySampling
import datadog.trace.common.writer.ListWriter
import io.opentracing.Scope
import io.opentracing.noop.NoopSpan
import spock.lang.Specification
import static datadog.opentracing.DDSpanContext.ORIGIN_KEY
@ -173,6 +175,33 @@ class DDSpanBuilderTest extends Specification {
actualContext.getTraceId() == spanId
}
def "should link to parent span implicitly"() {
setup:
final Scope parent = noopParent ?
tracer.scopeManager().activate(NoopSpan.INSTANCE, false) :
tracer.buildSpan("parent")
.startActive(false)
final String expectedParentId = noopParent ? "0" : parent.span().context().getSpanId()
final String expectedName = "fakeName"
final DDSpan span = tracer
.buildSpan(expectedName)
.start()
final DDSpanContext actualContext = span.context()
expect:
actualContext.getParentId() == expectedParentId
cleanup:
parent.close()
where:
noopParent << [false, true]
}
def "should inherit the DD parent attributes"() {
setup:
def expectedName = "fakeName"

View File

@ -96,19 +96,19 @@ class ScopeManagerTest extends Specification {
def "sets parent as current upon close"() {
setup:
def parentScope = tracer.buildSpan("parent").startActive(finishSpan)
def childScope = tracer.buildSpan("parent").startActive(finishSpan)
def childScope = noopChild ? tracer.scopeManager().activate(NoopSpan.INSTANCE, finishSpan) : tracer.buildSpan("parent").startActive(finishSpan)
expect:
scopeManager.active() == childScope
childScope.span().context().parentId == parentScope.span().context().spanId
childScope.span().context().trace == parentScope.span().context().trace
noopChild || childScope.span().context().parentId == parentScope.span().context().spanId
noopChild || childScope.span().context().trace == parentScope.span().context().trace
when:
childScope.close()
then:
scopeManager.active() == parentScope
spanFinished(childScope.span()) == finishSpan
noopChild || spanFinished(childScope.span()) == finishSpan
!spanFinished(parentScope.span())
writer == []
@ -116,13 +116,17 @@ class ScopeManagerTest extends Specification {
parentScope.close()
then:
spanFinished(childScope.span()) == finishSpan
noopChild || spanFinished(childScope.span()) == finishSpan
spanFinished(parentScope.span()) == finishSpan
writer == [[parentScope.span(), childScope.span()]] || !finishSpan
writer == [[parentScope.span(), childScope.span()]] || !finishSpan || noopChild
scopeManager.active() == null
where:
finishSpan << [true, false]
finishSpan | noopChild
true | false
false | false
true | true
false | true
}
def "ContinuableScope only creates continuations when propagation is set"() {
@ -568,7 +572,7 @@ class ScopeManagerTest extends Specification {
}
boolean spanFinished(Span span) {
return ((DDSpan) span).isFinished()
return ((DDSpan) span)?.isFinished()
}
class AtomicReferenceScope extends AtomicReference<Span> implements ScopeContext, Scope {

View File

@ -22,6 +22,7 @@ include ':utils:gc-utils'
include ':dd-smoke-tests:play'
include ':dd-smoke-tests:springboot'
include ':dd-smoke-tests:wildfly'
include ':dd-smoke-tests:cli'
// instrumentation:
include ':dd-java-agent:instrumentation:akka-http-10.0'
@ -41,6 +42,7 @@ include ':dd-java-agent:instrumentation:elasticsearch:transport-5'
include ':dd-java-agent:instrumentation:elasticsearch:transport-5.3'
include ':dd-java-agent:instrumentation:elasticsearch:transport-6'
include ':dd-java-agent:instrumentation:glassfish'
include ':dd-java-agent:instrumentation:google-http-client'
include ':dd-java-agent:instrumentation:grpc-1.5'
include ':dd-java-agent:instrumentation:hibernate'
include ':dd-java-agent:instrumentation:hibernate:core-3.3'