diff --git a/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java b/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java index a121c7543c1..7b2c4c1737f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java @@ -43,7 +43,7 @@ public void writeTracerMetadata(Config config) { } } - private static String generateFileName() { + static String generateFileName() { String suffix = RandomUtils.randomUUID().toString().substring(0, 8); return "datadog-tracer-info-" + suffix; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 18a88fd7b4a..64bf017e9db 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -298,8 +298,7 @@ private boolean interceptSpanType(DDSpanContext span, Object value) { return true; } - private boolean interceptServiceName( - RuleFlags.Feature feature, DDSpanContext span, Object value) { + boolean interceptServiceName(RuleFlags.Feature feature, DDSpanContext span, Object value) { if (ruleFlags.isEnabled(feature)) { String serviceName = String.valueOf(value); span.setServiceName(serviceName); @@ -336,7 +335,7 @@ private boolean interceptSamplingPriority(DDSpanContext span, Object value) { return false; } - private boolean interceptServletContext(DDSpanContext span, Object value) { + boolean interceptServletContext(DDSpanContext span, Object value) { // even though this tag is sometimes used to set the service name // (which has the side effect of marking the span as eligible for metrics // in the trace agent) we also want to store it in the tags no matter what, diff --git a/dd-trace-core/src/main/java/datadog/trace/core/util/GlobPattern.java b/dd-trace-core/src/main/java/datadog/trace/core/util/GlobPattern.java index 2b46a2181d4..7049f958b7a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/util/GlobPattern.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/util/GlobPattern.java @@ -9,7 +9,7 @@ public static Pattern globToRegexPattern(String globPattern) { return Pattern.compile(regex, Pattern.CASE_INSENSITIVE); } - private static String globToRegex(String globPattern) { + static String globToRegex(String globPattern) { StringBuilder sb = new StringBuilder(64); sb.append('^'); for (int i = 0; i < globPattern.length(); i++) { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/baggage/BaggagePropagatorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/baggage/BaggagePropagatorTest.groovy deleted file mode 100644 index 29eb3519aea..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/baggage/BaggagePropagatorTest.groovy +++ /dev/null @@ -1,295 +0,0 @@ -package datadog.trace.core.baggage - -import datadog.context.Context -import datadog.context.propagation.CarrierSetter -import datadog.context.propagation.CarrierVisitor -import datadog.trace.bootstrap.instrumentation.api.Baggage -import datadog.trace.bootstrap.instrumentation.api.ContextVisitors -import datadog.trace.test.util.DDSpecification - -import java.util.function.BiConsumer - -import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_BYTES -import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_ITEMS -import static datadog.trace.core.baggage.BaggagePropagator.BAGGAGE_KEY - -class BaggagePropagatorTest extends DDSpecification { - BaggagePropagator propagator - CarrierSetter setter - Map carrier - Context context - - static class MapCarrierAccessor - implements CarrierSetter>, CarrierVisitor> { - @Override - void set(Map carrier, String key, String value) { - if (carrier != null && key != null && value != null) { - carrier.put(key, value) - } - } - - @Override - void forEachKeyValue(Map carrier, BiConsumer visitor) { - carrier.forEach(visitor) - } - } - - def setup() { - this.propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) - this.setter = new MapCarrierAccessor() - this.carrier = [:] - this.context = Context.root() - } - - def 'test baggage propagator context injection'() { - setup: - this.context = Baggage.create(baggageMap).storeInto(this.context) - - when: - this.propagator.inject(context, carrier, setter) - - then: - assert carrier[BAGGAGE_KEY] == baggageHeader - - where: - baggageMap | baggageHeader - ["key1": "val1", "key2": "val2", "foo": "bar"] | "key1=val1,key2=val2,foo=bar" - ['",;\\()/:<=>?@[]{}': '",;\\'] | "%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C" - [key1: "val1"] | "key1=val1" - [key1: "val1", key2: "val2"] | "key1=val1,key2=val2" - [serverNode: "DF 28"] | "serverNode=DF%2028" - [userId: "Amélie"] | "userId=Am%C3%A9lie" - ["user!d(me)": "false"] | "user!d%28me%29=false" - ["abcdefg": "hijklmnopq♥"] | "abcdefg=hijklmnopq%E2%99%A5" - } - - def "test baggage inject item limit"() { - setup: - propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config - context = Baggage.create(baggage).storeInto(context) - - when: - this.propagator.inject(context, carrier, setter) - - then: - assert carrier[BAGGAGE_KEY] == baggageHeader - - where: - baggage | baggageHeader - [key1: "val1", key2: "val2"] | "key1=val1,key2=val2" - [key1: "val1", key2: "val2", key3: "val3"] | "key1=val1,key2=val2" - } - - def "test baggage inject bytes limit"() { - setup: - propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20) //creating a new instance after injecting config - context = Baggage.create(baggage).storeInto(context) - - when: - this.propagator.inject(context, carrier, setter) - - then: - assert carrier[BAGGAGE_KEY] == baggageHeader - - where: - baggage | baggageHeader - [key1: "val1", key2: "val2"] | "key1=val1,key2=val2" - [key1: "val1", key2: "val2", key3: "val3"] | "key1=val1,key2=val2" - ["abcdefg": "hijklmnopq♥"] | "" - } - - def 'test tracing propagator context extractor'() { - setup: - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage.fromContext(context).asMap() == baggageMap - - where: - baggageHeader | baggageMap - "key1=val1,key2=val2,foo=bar" | ["key1": "val1", "key2": "val2", "foo": "bar"] - "%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C" | ['",;\\()/:<=>?@[]{}': '",;\\'] - } - - def "test extracting non ASCII headers"() { - setup: - def headers = [ - (BAGGAGE_KEY) : "key1=vallée,clé2=value", - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - def baggage = Baggage.fromContext(context) - - then: 'non ASCII values data are still accessible as part of the API' - baggage != null - baggage.asMap().get('key1') == 'vallée' - baggage.asMap().get('clé2') == 'value' - baggage.w3cHeader == null - - - when: - this.propagator.inject(Context.root().with(baggage), carrier, setter) - - then: 'baggage are URL encoded if not valid, even if not modified' - assert carrier[BAGGAGE_KEY] == 'key1=vall%C3%A9e,cl%C3%A92=value' - } - - def "extract invalid baggage headers"() { - setup: - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage.fromContext(context) == null - - where: - baggageHeader | _ - "no-equal-sign,foo=gets-dropped-because-previous-pair-is-malformed" | _ - "foo=gets-dropped-because-subsequent-pair-is-malformed,=" | _ - "=no-key" | _ - "no-value=" | _ - "" | _ - ",," | _ - "=" | _ - } - - def "test baggage cache"(){ - setup: - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage baggageContext = Baggage.fromContext(context) - baggageContext.w3cHeader == cachedString - - where: - baggageHeader | cachedString - "key1=val1,key2=val2,foo=bar" | "key1=val1,key2=val2,foo=bar" - '";\\()/:<=>?@[]{}=";\\' | null - } - - def "test baggage cache items limit"(){ - setup: - propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage baggageContext = Baggage.fromContext(context) - baggageContext.getW3cHeader() as String == cachedString - - where: - baggageHeader | cachedString - "key1=val1,key2=val2" | "key1=val1,key2=val2" - "key1=val1,key2=val2,key3=val3" | "key1=val1,key2=val2" - "key1=val1,key2=val2,key3=val3,key4=val4" | "key1=val1,key2=val2" - } - - def "test baggage cache bytes limit"(){ - setup: - propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage baggageContext = Baggage.fromContext(context) - baggageContext.getW3cHeader() as String == cachedString - - where: - baggageHeader | cachedString - "key1=val1,key2=val2" | "key1=val1,key2=val2" - "key1=val1,key2=val2,key3=val3" | "key1=val1,key2=val2" - } - - def "test baggage extract items limit"() { - setup: - propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: 'parsing stops once the item limit is exceeded' - Baggage.fromContext(context).asMap() == baggageMap - - where: - baggageHeader | baggageMap - "key1=val1" | [key1: "val1"] - "key1=val1,key2=val2" | [key1: "val1", key2: "val2"] - "key1=val1,key2=val2,key3=val3" | [key1: "val1", key2: "val2"] - } - - def "test baggage extract bytes limit"() { - setup: - propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : baggageHeader, - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: 'parsing stops once the byte limit is exceeded' - Baggage.fromContext(context).asMap() == baggageMap - - where: - baggageHeader | baggageMap - "key1=val1" | [key1: "val1"] - "key1=val1,key2=val2" | [key1: "val1", key2: "val2"] - "key1=val1,key2=val2,key3=val3" | [key1: "val1", key2: "val2"] - } - - def "test baggage extract 0 item limit"() { - setup: - propagator = new BaggagePropagator(true, true, 0, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : "key1=value1", - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage.fromContext(context) == null - } - - - - def "test baggage extract 0 byte limit"() { - setup: - propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 0) //creating a new instance after injecting config - def headers = [ - (BAGGAGE_KEY) : "key1=value1", - ] - - when: - context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()) - - then: - Baggage.fromContext(context) == null - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.groovy deleted file mode 100644 index 3e8386867b2..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -package datadog.trace.core.servicediscovery - -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString -import datadog.trace.core.test.DDCoreSpecification -import spock.lang.Timeout -import org.msgpack.core.MessagePack -import org.msgpack.value.MapValue - - -@Timeout(10) -class ServiceDiscoveryTest extends DDCoreSpecification { - def "encodePayload with all optional fields"() { - given: - String tracerVersion = "1.2.3" - String hostname = "test-host" - String runtimeID = "rid-123" - String service = "orders" - String env = "prod" - String serviceVersion = "1.1.1" - UTF8BytesString processTags = UTF8BytesString.create("key1:val1,key2:val2") - String containerID = "containerID" - boolean appLogsCollectionEnabled = true - - when: - byte[] out = ServiceDiscovery.encodePayload(tracerVersion, hostname, appLogsCollectionEnabled, runtimeID, service, env, serviceVersion, processTags, containerID) - MapValue map = MessagePack.newDefaultUnpacker(out).unpackValue().asMapValue() - - then: - map.size() == 11 - and: - map.toString() == '{"schema_version":2,"tracer_language":"java","tracer_version":"1.2.3","hostname":"test-host","logs_collected":true,"runtime_id":"rid-123","service_name":"orders","service_env":"prod","service_version":"1.1.1","process_tags":"key1:val1,key2:val2","container_id":"containerID"}' - } - - def "encodePayload only required fields"() { - given: - String tracerVersion = "1.2.3" - String hostname = "my_host" - - when: - byte[] out = ServiceDiscovery.encodePayload(tracerVersion, hostname, false, null, null, null, null, null, null) - MapValue map = MessagePack.newDefaultUnpacker(out).unpackValue().asMapValue() - - then: - map.size() == 5 - and: - map.toString() == '{"schema_version":2,"tracer_language":"java","tracer_version":"1.2.3","hostname":"my_host","logs_collected":false}' - } - def "generateFileName"() { - when: - String name = ServiceDiscovery.generateFileName() - - then: - name.startsWith("datadog-tracer-info-") - name.length() == "datadog-tracer-info-".length() + 8 - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy deleted file mode 100644 index 4c57c668f2c..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy +++ /dev/null @@ -1,755 +0,0 @@ -package datadog.trace.core.taginterceptor - -import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVICE_NAME -import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME -import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE -import datadog.trace.api.ProductTraceSource -import static datadog.trace.api.config.TracerConfig.SPLIT_BY_TAGS - -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.DDTags -import datadog.trace.api.config.GeneralConfig -import datadog.trace.api.env.CapturedEnvironment -import datadog.trace.api.remoteconfig.ServiceNameCollector -import datadog.trace.api.sampling.PrioritySampling -import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.common.sampling.AllSampler -import datadog.trace.common.writer.ListWriter -import datadog.trace.common.writer.LoggingWriter -import datadog.trace.core.CoreSpan -import datadog.trace.core.DDSpanContext -import datadog.trace.core.test.DDCoreSpecification - -class TagInterceptorTest extends DDCoreSpecification { - def setup() { - injectSysConfig(SPLIT_BY_TAGS, "sn.tag1,sn.tag2") - } - - def "set service name"() { - setup: - injectSysConfig("dd.trace.PeerServiceTagInterceptor.enabled", "true") - def tracer = tracerBuilder() - .serviceName("wrong-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .serviceNameMappings(mapping) - .build() - - when: - def span = tracer.buildSpan("datadog", "some span").withTag(tag, name).start() - span.finish() - - then: - span.getServiceName() == expected - - cleanup: - tracer.close() - - where: - tag | name | expected - DDTags.SERVICE_NAME | "some-service" | "new-service" - DDTags.SERVICE_NAME | "other-service" | "other-service" - "service" | "some-service" | "new-service" - "service" | "other-service" | "other-service" - Tags.PEER_SERVICE | "some-service" | "new-service" - Tags.PEER_SERVICE | "other-service" | "other-service" - "sn.tag1" | "some-service" | "new-service" - "sn.tag1" | "other-service" | "other-service" - "sn.tag2" | "some-service" | "new-service" - "sn.tag2" | "other-service" | "other-service" - - mapping = ["some-service": "new-service"] - } - - def "default or configured service name can be remapped without setting tag"() { - setup: - def tracer = tracerBuilder() - .serviceName(serviceName) - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .serviceNameMappings(mapping) - .build() - - when: - def span = tracer.buildSpan("datadog", "some span").start() - span.finish() - - then: - span.serviceName == expected - - cleanup: - tracer.close() - - where: - serviceName | expected | mapping - DEFAULT_SERVICE_NAME | DEFAULT_SERVICE_NAME | ["other-service-name": "other-service"] - DEFAULT_SERVICE_NAME | "new-service" | [(DEFAULT_SERVICE_NAME): "new-service"] - "other-service-name" | "other-service" | ["other-service-name": "other-service"] - } - - def "set service name from servlet.context with context '#context'"() { - when: - def tracer = tracerBuilder().writer(new ListWriter()).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setTag(DDTags.SERVICE_NAME, serviceName) - span.setTag("servlet.context", context) - - then: - span.serviceName == expected - - cleanup: - tracer.close() - - where: - context | serviceName | expected - "/" | DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME | DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME - "" | DEFAULT_SERVICE_NAME | DEFAULT_SERVICE_NAME - "/some-context" | DEFAULT_SERVICE_NAME | "some-context" - "other-context" | DEFAULT_SERVICE_NAME | "other-context" - "/" | "my-service" | "my-service" - "" | "my-service" | "my-service" - "/some-context" | "my-service" | "my-service" - "other-context" | "my-service" | "my-service" - } - - def "setting service name as a property disables servlet.context with context '#context'"() { - when: - injectSysConfig("service", serviceName) - def tracer = tracerBuilder().writer(new ListWriter()).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setTag("servlet.context", context) - - then: - span.serviceName == serviceName - - cleanup: - tracer.close() - - where: - context | serviceName - "/" | DEFAULT_SERVICE_NAME - "" | DEFAULT_SERVICE_NAME - "/some-context" | DEFAULT_SERVICE_NAME - "other-context" | DEFAULT_SERVICE_NAME - "/" | CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME) - "" | CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME) - "/some-context" | CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME) - "other-context" | CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME) - "/" | "my-service" - "" | "my-service" - "/some-context" | "my-service" - "other-context" | "my-service" - } - - def "mapping causes servlet.context to not change service name"() { - setup: - def tracer = tracerBuilder() - .serviceName(serviceName) - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .serviceNameMappings(mapping) - .build() - - when: - def span = tracer.buildSpan("datadog", "some span").start() - span.setTag("servlet.context", context) - span.finish() - - then: - span.serviceName == "new-service" - - cleanup: - tracer.close() - - where: - context | serviceName - "/some-context" | DEFAULT_SERVICE_NAME - "/some-context" | "my-service" - - mapping = [(serviceName): "new-service"] - } - - def createSplittingTracer(tag) { - return tracerBuilder() - .serviceName("my-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - // equivalent to split-by-tags: tag - .tagInterceptor(new TagInterceptor(true, "my-service", - Collections.singleton(tag), new RuleFlags(), false)) - .build() - } - - def "split-by-tags for servlet.context and experimental jee split by deployment is #jeeActive"() { - setup: - def tracer = tracerBuilder() - .serviceName("my-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .tagInterceptor(new TagInterceptor(false, "my-service", - Collections.emptySet(), new RuleFlags(), jeeActive)) - .build() - when: - def span = tracer.buildSpan("datadog", "some span").start() - span.setTag(InstrumentationTags.SERVLET_CONTEXT, "some-context") - span.finish() - - then: - span.serviceName == expected - - cleanup: - tracer.close() - - where: - expected | jeeActive - "some-context" | false - "my-service" | true - } - - def "peer.service then split-by-tags via builder"() { - setup: - def tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION) - - when: - def span = tracer.buildSpan("datadog", "some span") - .withTag(Tags.PEER_SERVICE, "peer-service") - .withTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") - .start() - span.finish() - - then: - span.serviceName == "some-queue" - - cleanup: - tracer.close() - } - - def "peer.service then split-by-tags via setTag"() { - setup: - def tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION) - - when: - def span = tracer.buildSpan("datadog", "some span").start() - span.setTag(Tags.PEER_SERVICE, "peer-service") - span.setTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") - span.finish() - - then: - span.serviceName == "some-queue" - - cleanup: - tracer.close() - } - - def "split-by-tags then peer-service via builder"() { - setup: - injectSysConfig("dd.trace.PeerServiceTagInterceptor.enabled", "$enabled") - def tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION) - - when: - def span = tracer.buildSpan("datadog", "some span") - .withTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") - .withTag(Tags.PEER_SERVICE, "peer-service") - .start() - span.finish() - - then: - (span.serviceName == "peer-service") == enabled - - cleanup: - tracer.close() - - where: - enabled << [true, false] - } - - def "split-by-tags then peer-service via setTag"() { - setup: - injectSysConfig("dd.trace.PeerServiceTagInterceptor.enabled", "true") - def tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION) - - when: - def span = tracer.buildSpan("datadog", "some span").start() - span.setTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") - span.setTag(Tags.PEER_SERVICE, "peer-service") - span.finish() - - then: - span.serviceName == "peer-service" - - cleanup: - tracer.close() - } - - def "set resource name"() { - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - def span = tracer.buildSpan("datadog", "test").start() - span.setTag(DDTags.RESOURCE_NAME, name) - span.finish() - writer.waitForTraces(1) - - then: - span.getResourceName() == name - - cleanup: - tracer.close() - - where: - name = "my resource name" - } - - def "set resource name ignores null"() { - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - def span = tracer.buildSpan("datadog", "test").withResourceName("keep").start() - span.setTag(DDTags.RESOURCE_NAME, null) - span.finish() - writer.waitForTraces(1) - - then: - span.getResourceName() == "keep" - - cleanup: - tracer.close() - } - - def "set span type"() { - when: - def tracer = tracerBuilder().writer(new ListWriter()).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setSpanType(type) - span.finish() - - then: - span.getSpanType() == type - - cleanup: - tracer.close() - - where: - type = DDSpanTypes.HTTP_CLIENT - } - - def "set span type with tag"() { - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setTag(DDTags.SPAN_TYPE, type) - span.finish() - writer.waitForTraces(1) - - then: - span.getSpanType() == type - - cleanup: - tracer.close() - - where: - type = DDSpanTypes.HTTP_CLIENT - } - - def "span metrics starts empty but added with rate limiting value of #rate"() { - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - def span = tracer.buildSpan("datadog", "test").start() - - then: - span.getTag(ANALYTICS_SAMPLE_RATE) == null - - when: - span.setTag(ANALYTICS_SAMPLE_RATE, rate) - span.finish() - writer.waitForTraces(1) - - then: - span.getTag(ANALYTICS_SAMPLE_RATE) == result - - cleanup: - tracer.close() - - where: - rate | result - 00 | 0 - 1 | 1 - 0f | 0 - 1f | 1 - 0.1 | 0.1 - 1.1 | 1.1 - -1 | -1 - 10 | 10 - "00" | 0 - "1" | 1 - "1.0" | 1 - "0" | 0 - "0.1" | 0.1 - "1.1" | 1.1 - "-1" | -1 - "str" | null - } - - def "set priority sampling via tag"() { - when: - def tracer = tracerBuilder().writer(new ListWriter()).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setTag(tag, value) - - then: - span.samplingPriority == expected - - cleanup: - tracer.close() - - where: - tag | value | expected - DDTags.MANUAL_KEEP | true | PrioritySampling.USER_KEEP - DDTags.MANUAL_KEEP | false | null - DDTags.MANUAL_KEEP | "true" | PrioritySampling.USER_KEEP - DDTags.MANUAL_KEEP | "false" | null - DDTags.MANUAL_KEEP | "asdf" | null - - DDTags.MANUAL_DROP | true | PrioritySampling.USER_DROP - DDTags.MANUAL_DROP | false | null - DDTags.MANUAL_DROP | "true" | PrioritySampling.USER_DROP - DDTags.MANUAL_DROP | "false" | null - DDTags.MANUAL_DROP | "asdf" | null - - Tags.ASM_KEEP | true | PrioritySampling.USER_KEEP - Tags.ASM_KEEP | false | null - Tags.ASM_KEEP | "true" | PrioritySampling.USER_KEEP - Tags.ASM_KEEP | "false" | null - Tags.ASM_KEEP | "asdf" | null - - Tags.SAMPLING_PRIORITY | -1 | PrioritySampling.USER_DROP - Tags.SAMPLING_PRIORITY | 0 | PrioritySampling.USER_DROP - Tags.SAMPLING_PRIORITY | 1 | PrioritySampling.USER_KEEP - Tags.SAMPLING_PRIORITY | 2 | PrioritySampling.USER_KEEP - Tags.SAMPLING_PRIORITY | "-1" | PrioritySampling.USER_DROP - Tags.SAMPLING_PRIORITY | "0" | PrioritySampling.USER_DROP - Tags.SAMPLING_PRIORITY | "1" | PrioritySampling.USER_KEEP - Tags.SAMPLING_PRIORITY | "2" | PrioritySampling.USER_KEEP - Tags.SAMPLING_PRIORITY | "asdf" | null - } - - def "set error flag when error tag reported"() { - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - def span = tracer.buildSpan("datadog", "test").start() - span.setTag(Tags.ERROR, error) - span.finish() - writer.waitForTraces(1) - - then: - span.isError() == error - - cleanup: - tracer.close() - - where: - error | _ - true | _ - false | _ - } - - def "#attribute interceptors apply to builder too"() { - setup: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - when: - def span = tracer.buildSpan("datadog", "interceptor.test").withTag(name, value).start() - span.finish() - writer.waitForTraces(1) - - then: - span.context()."$attribute" == value - - cleanup: - tracer.close() - - where: - attribute | name | value - "serviceName" | DDTags.SERVICE_NAME | "my-service" - "resourceName" | DDTags.RESOURCE_NAME | "my-resource" - "spanType" | DDTags.SPAN_TYPE | "my-span-type" - } - - def "decorators apply to builder too"() { - setup: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - when: - def span = tracer.buildSpan("datadog", "decorator.test").withTag("sn.tag1", "some val").start() - span.finish() - writer.waitForTraces(1) - - then: - span.serviceName == "some val" - - when: - span = tracer.buildSpan("datadog", "decorator.test").withTag("servlet.context", "/my-servlet").start() - - then: - span.serviceName == "my-servlet" - - when: - span = tracer.buildSpan("datadog", "decorator.test").withTag("error", "true").start() - span.finish() - writer.waitForTraces(2) - - then: - span.error - - when: - span = tracer.buildSpan("datadog", "decorator.test").withTag(Tags.DB_STATEMENT, "some-statement").start() - span.finish() - writer.waitForTraces(3) - - then: - span.resourceName.toString() == "some-statement" - - cleanup: - tracer.close() - } - - def "disable decorator via config"() { - setup: - injectSysConfig("dd.trace.${decorator}.enabled", "$enabled") - - def tracer = tracerBuilder() - .serviceName("some-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .build() - - when: - def span = tracer.buildSpan("datadog", "some span").withTag(DDTags.SERVICE_NAME, "other-service").start() - span.finish() - - then: - span.getServiceName() == enabled ? "other-service" : "some-service" - - cleanup: - tracer.close() - - where: - decorator | enabled - "servicenametaginterceptor" | true - "ServiceNameTagInterceptor" | true - "serviceNametaginterceptor" | false - "ServiceNameTagInterceptor" | false - } - - def "disabling service decorator does not disable split by tags"() { - setup: - injectSysConfig("dd.trace.ServiceNameTagInterceptor.enabled", "false") - - def tracer = tracerBuilder() - .serviceName("some-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .build() - - when: - def span = tracer.buildSpan("datadog", "some span").withTag(tag, name).start() - span.finish() - - then: - span.getServiceName() == expected - - cleanup: - tracer.close() - - where: - tag | name | expected - DDTags.SERVICE_NAME | "new-service" | "some-service" - "service" | "new-service" | "some-service" - "sn.tag1" | "new-service" | "new-service" - } - - def "change top level status when changing service name"() { - setup: - def tracer = tracerBuilder() - .serviceName("some-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .build() - - AgentSpan parent = tracer.buildSpan("datadog", "parent") - .withServiceName("parent").start() - - when: "the service name doesn't match the parent" - AgentSpan child = tracer.buildSpan("datadog", "child") - .withServiceName("child") - .asChildOf(parent) - .start() - - then: - (child as CoreSpan).isTopLevel() - - when: "the service name is changed to match the parent" - child.setTag(DDTags.SERVICE_NAME, "parent") - - then: - !(child as CoreSpan).isTopLevel() - - when: "the service name is changed to no longer match the parent" - child.setTag(DDTags.SERVICE_NAME, "foo") - - then: - (child as CoreSpan).isTopLevel() - - cleanup: - tracer.close() - } - - def "treat `1` value as `true` for boolean tag values"() { - setup: - def tracer = tracerBuilder() - .serviceName("some-service") - .writer(new LoggingWriter()) - .sampler(new AllSampler()) - .build() - - when: - AgentSpan span = tracer.buildSpan("datadog", "test").start() - - then: - span.getSamplingPriority() == null - - when: - span.setTag(tag, value) - - then: - span.getSamplingPriority() == samplingPriority - - where: - tag | value | samplingPriority - DDTags.MANUAL_DROP | true | PrioritySampling.USER_DROP - DDTags.MANUAL_DROP | "1" | PrioritySampling.USER_DROP - DDTags.MANUAL_DROP | false | null - DDTags.MANUAL_DROP | "0" | null - DDTags.MANUAL_KEEP | true | PrioritySampling.USER_KEEP - DDTags.MANUAL_KEEP | "1" | PrioritySampling.USER_KEEP - DDTags.MANUAL_KEEP | false | null - DDTags.MANUAL_KEEP | "0" | null - } - - def "URLAsResourceNameRule sets the resource name"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).build() - - def span = tracer.buildSpan("datadog", "fakeOperation").start() - meta.each { - span.setTag(it.key, (String) it.value) - } - - when: - span.setTag(Tags.HTTP_URL, value) - - then: - span.resourceName.toString() == resourceName - - cleanup: - span.finish() - tracer.close() - - where: - value | resourceName | meta - null | "fakeOperation" | [:] - " " | "/" | [:] - "\t" | "/" | [:] - "/path" | "/path" | [:] - "/ABC/a-1/b_2/c.3/d4d/5f/6" | "/ABC/?/?/?/?/?/?" | [:] - "/not-found" | "404" | [(Tags.HTTP_STATUS): "404"] - "/with-method" | "POST /with-method" | [(Tags.HTTP_METHOD): "Post"] - - ignore = meta.put(Tags.HTTP_URL, value) - } - - def "when user sets peer.service the source should be peer.service"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).build() - - def span = tracer.buildSpan("datadog", "fakeOperation").start() - - - when: - span.setTag(Tags.PEER_SERVICE, "test") - - then: - span.getTag(DDTags.PEER_SERVICE_SOURCE) == "peer.service" - - cleanup: - span.finish() - tracer.close() - } - - void "when interceptServiceName extraServiceProvider is called"() { - def origServiceNameCollector = ServiceNameCollector.INSTANCE - setup: - final extraServiceProvider = Mock(ServiceNameCollector) - ServiceNameCollector.INSTANCE = extraServiceProvider - final ruleFlags = Mock(RuleFlags) - ruleFlags.isEnabled(_) >> true - final interceptor = new TagInterceptor(true, "my-service", Collections.singleton(DDTags.SERVICE_NAME), ruleFlags, false) - - when: - interceptor.interceptServiceName(null, Mock(DDSpanContext), "some-service") - - then: - 1 * extraServiceProvider.addService("some-service") - - cleanup: - ServiceNameCollector.INSTANCE = origServiceNameCollector - } - - void "when interceptServletContext extraServiceProvider is called"() { - def origServiceNameCollector = ServiceNameCollector.INSTANCE - setup: - final extraServiceProvider = Mock(ServiceNameCollector) - ServiceNameCollector.INSTANCE = extraServiceProvider - final ruleFlags = Mock(RuleFlags) - ruleFlags.isEnabled(_) >> true - final interceptor = new TagInterceptor(true, "my-service", Collections.singleton("servlet.context"), ruleFlags, false) - - when: - interceptor.interceptServletContext(Mock(DDSpanContext), value) - - then: - 1 * extraServiceProvider.addService(expected) - - cleanup: - ServiceNameCollector.INSTANCE = origServiceNameCollector - - where: - value | expected - "/" | "root-servlet" - "/test" | "test" - "test" | "test" - } - - void "When intercepts product trace source propagation tag updatePropagatedTraceSource is called"() { - setup: - final ruleFlags = Mock(RuleFlags) - ruleFlags.isEnabled(_) >> true - final interceptor = new TagInterceptor(ruleFlags) - final context = Mock(DDSpanContext) - - when: - interceptor.interceptTag(context, Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM) - - then: - 1 * context.addPropagatedTraceSource(ProductTraceSource.ASM) - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.groovy deleted file mode 100644 index e0ed91db723..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.groovy +++ /dev/null @@ -1,48 +0,0 @@ -package datadog.trace.core.traceinterceptor - -import datadog.trace.api.DDTags -import datadog.trace.common.writer.ListWriter - -import datadog.trace.core.test.DDCoreSpecification - -import spock.lang.Timeout - -@Timeout(10) -class LatencyTraceInterceptorTest extends DDCoreSpecification { - - - def "test set sampling priority according to latency"() { - setup: - - injectSysConfig("trace.partial.flush.enabled", partialFlushEnabled) - injectSysConfig("trace.experimental.keep.latency.threshold.ms", latencyThreshold) - - when: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - def spanSetup = tracer.buildSpan("test","my_operation_name").withTag(priorityTag, true).start() - sleep(minDuration) - spanSetup.finish() - - then: - def trace = writer.firstTrace() - trace.size() == 1 - def span = trace[0] - span.context().getSamplingPriority() == expected - - cleanup: - tracer.close() - - where: - partialFlushEnabled | latencyThreshold | priorityTag | minDuration | expected - "true" | "200" | DDTags.MANUAL_KEEP | 10 | 2 - "true" | "200" | DDTags.MANUAL_DROP | 10 | -1 - "true" | "200" | DDTags.MANUAL_KEEP | 300 | 2 - "true" | "200" | DDTags.MANUAL_DROP | 300 | -1 - "false" | "200" | DDTags.MANUAL_KEEP | 10 | 2 - "false" | "200" | DDTags.MANUAL_DROP | 10 | -1 - "false" | "200" | DDTags.MANUAL_KEEP | 300 | 2 - "false" | "200" | DDTags.MANUAL_DROP | 300 | 2 - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/util/GlobPatternTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/util/GlobPatternTest.groovy deleted file mode 100644 index 9af94dc894c..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/util/GlobPatternTest.groovy +++ /dev/null @@ -1,22 +0,0 @@ -package datadog.trace.core.util - -import datadog.trace.test.util.DDSpecification - -class GlobPatternTest extends DDSpecification { - - def "Convert glob pattern to regex"() { - expect: - GlobPattern.globToRegex(globPattern) == expectedRegex - - where: - globPattern | expectedRegex - "*" | "^.*\$" - "?" | "^.\$" - "??" | "^..\$" - "Foo*" | "^Foo.*\$" - "abc" | "^abc\$" - "?" | "^.\$" - "F?o" | "^F.o\$" - "Bar*" | "^Bar.*\$" - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/util/LRUCacheTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/util/LRUCacheTest.groovy deleted file mode 100644 index baa4d195691..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/util/LRUCacheTest.groovy +++ /dev/null @@ -1,45 +0,0 @@ -package datadog.trace.core.util - -import datadog.trace.test.util.DDSpecification - -class LRUCacheTest extends DDSpecification { - def "Should eject least recently used element"() { - when: - def lruCache = new LRUCache(5) - for (int i = 1; i <= 5; i++) { - lruCache.put(i, String.valueOf(i)) - } - // now look at 2 values - lruCache.get(1) - lruCache.get(3) - // now insert 2 new values - lruCache.put(6, "6") - lruCache.put(7, "7") - - then: - lruCache.size() == 5 - lruCache.values().containsAll(Arrays.asList("1", "3", "5", "6", "7")) - } - - def "Should notify listener when ejecting least recently used element"() { - setup: - List ejected = new ArrayList<>() - LRUCache.ExpiryListener listener = { Map.Entry entry -> - ejected.add(entry.getKey()) - } - when: - def lruCache = new LRUCache(listener, 10, 0.75f, 5) - for (int i = 1; i <= 5; i++) { - lruCache.put(i, String.valueOf(i)) - } - // now look at 2 values - lruCache.get(1) - lruCache.get(3) - // now insert 2 new values - lruCache.put(6, "6") - lruCache.put(7, "7") - - then: - ejected == [2, 4] - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/util/MatchersTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/util/MatchersTest.groovy deleted file mode 100644 index 1b4cd0c95a9..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/util/MatchersTest.groovy +++ /dev/null @@ -1,119 +0,0 @@ -package datadog.trace.core.util - -import datadog.trace.test.util.DDSpecification - -class MatchersTest extends DDSpecification { - - def "match-all scenarios must return an any matcher"() { - expect: - Matchers.compileGlob(glob) instanceof Matchers.AnyMatcher - - where: - glob << [null, "*", "**"] - } - - def "pattern without * or ? must be an EqualsMatcher"() { - expect: - Matchers.compileGlob(glob) instanceof Matchers.InsensitiveEqualsMatcher - - where: - glob << ["a", "ogre", "bcoho34e2"] - } - - def "pattern with either * or ? must be a PatternMatcher"() { - expect: - Matchers.compileGlob(glob) instanceof Matchers.PatternMatcher - - where: - glob << ["?", "foo*", "*bar", "F?oB?r", "F?o*", "?*", "*?"] - } - - def "an exact matcher is self matching"() { - expect: - Matchers.compileGlob(pattern).matches(pattern) - - where: - pattern << ["", "a", "abc", "cde"] - } - - def "a pattern matcher test #iterationIndex #pattern"() { - when: - def matcher = Matchers.compileGlob(pattern) - - then: - matcher.matches(value) == matches - - where: - pattern | value | matches - "fo?" | "Foo" | true - "Fo?" | "Foo" | true - "Fo?" | new StringBuilder("Foo") | true - "Fo?" | new StringBuilder("foo") | true - "Foo" | new StringBuilder("foo") | true - "bar" | new StringBuilder("Baz") | false - "Fo?" | "Fooo" | false - "Fo*" | "Fo" | true - "Fo*" | "Fa" | false - "F*B?r" | "FooBar" | true - "F*B?r" | "FooFar" | false - "f*b?r" | "FooBar" | true - "*" | true | true - "true" | true | true - "false" | false | true - "TRUE" | true | true - "FALSE" | false | true - "True" | true | true - "False" | false | true - "T*" | true | true - "F*" | false | true - "" | "" | true - "" | "non-empty" | false - "*" | "foo" | true - "**" | "foo" | true - "???" | "foo" | true - "*" | 20 | true - "20" | 20 | true - "-20" | -20 | true - "*" | (byte)20 | true - "20" | (byte)20 | true - "*" | (short)20 | true - "20" | (short)20 | true - "*" | 20L | true - "20" | 20L | true - "*" | 20F | true - "20" | 20F | true - "*" | 20D | true - "20" | 20D | true - "20" | bigInteger("20") | true - "20" | bigDecimal("20") | true - "2*" | 20.1F | false - "2*" | 20.1D | false - "2*" | bigDecimal("20.1") | false - "*" | new Object() {} | true - "**" | new Object() {} | true - "?" | new Object() {} | false - "*" | null | true - "?" | null | false - "[a-z]" | "[a-z]" | true - "[a-z]" | "a" | false - "[abc]" | "[abc]" | true - "[AbC]" | "[abc]" | true - "[Ab]" | new StringBuffer("[ab]") | true - "[abc]" | "a" | false - "[!ab]" | "[!ab]" | true - "[!ab]" | "c" | false - "^" | "^" | true - "()" | "()" | true - "(*)" | "(-)" | true - "\$" | "\$" | true - } - - // helper functions - to subvert codenarc - static bigInteger(str) { - return new BigInteger(str) - } - - static bigDecimal(str) { - return new BigDecimal(str) - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/util/SimpleRateLimiterTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/util/SimpleRateLimiterTest.groovy deleted file mode 100644 index eb2d505071e..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/util/SimpleRateLimiterTest.groovy +++ /dev/null @@ -1,71 +0,0 @@ -package datadog.trace.core.util - -import datadog.trace.api.time.ControllableTimeSource -import datadog.trace.test.util.DDSpecification - -import java.util.concurrent.TimeUnit - -class SimpleRateLimiterTest extends DDSpecification { - def "initial rate available at creation"() { - setup: - def timeSource = new ControllableTimeSource() - def limiter = new SimpleRateLimiter(rate, timeSource) - - when: - rate.times { - assert limiter.tryAcquire(): "failed for $it" - } - - then: - assert !limiter.tryAcquire() - - where: - rate << [10, 100, 1000] - } - - def "tokens never go beyond rate"() { - setup: - def timeSource = new ControllableTimeSource() - def limiter = new SimpleRateLimiter(rate, timeSource) - - when: - timeSource.advance(TimeUnit.SECONDS.toNanos(5)) - rate.times { - assert limiter.tryAcquire(): "failed for $it" - } - - then: - assert !limiter.tryAcquire() - - where: - rate << [10, 100, 1000] - } - - def "tokens are consumed and replenished"() { - setup: - def timeSource = new ControllableTimeSource() - def limiter = new SimpleRateLimiter(rate, timeSource) - long nanosIncrement = (long) (TimeUnit.SECONDS.toNanos(1) / (rate + 1)) + 1 - - when: - rate.times { - timeSource.advance(nanosIncrement) - assert limiter.tryAcquire(): "failed for $it" - } - - then: - assert !limiter.tryAcquire() - - when: - rate.times { - timeSource.advance(nanosIncrement) - assert limiter.tryAcquire(): "failed for $it" - } - - then: - assert !limiter.tryAcquire() - - where: - rate << [10, 100, 1000] - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/util/SystemAccessTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/util/SystemAccessTest.groovy deleted file mode 100644 index 4f908c1862a..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/util/SystemAccessTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -package datadog.trace.core.util - -import datadog.trace.test.util.DDSpecification - -import static datadog.trace.api.config.GeneralConfig.HEALTH_METRICS_ENABLED -import static datadog.trace.api.config.ProfilingConfig.PROFILING_ENABLED - -class SystemAccessTest extends DDSpecification { - def cleanup() { - SystemAccess.disableJmx() - } - - def "Test cpu time"() { - setup: - injectSysConfig(PROFILING_ENABLED, profilingEnabled.toString()) - injectSysConfig(HEALTH_METRICS_ENABLED, healthMetricsEnabled.toString()) - - if (providerEnabled) { - SystemAccess.enableJmx() - } else { - SystemAccess.disableJmx() - } - - when: - def threadCpuTime1 = SystemAccess.getCurrentThreadCpuTime() - // burn some cpu - def sum = 0 - for (int i = 0; i < 10_000; i++) { - sum += i - } - def threadCpuTime2 = SystemAccess.getCurrentThreadCpuTime() - - then: - sum > 0 - - if (hasCpuTime) { - assert threadCpuTime1 != Long.MIN_VALUE - assert threadCpuTime2 != Long.MIN_VALUE - assert threadCpuTime2 > threadCpuTime1 - } else { - assert threadCpuTime1 == Long.MIN_VALUE - assert threadCpuTime2 == Long.MIN_VALUE - } - - where: - providerEnabled | profilingEnabled | healthMetricsEnabled | hasCpuTime - false | false | false | false - false | false | true | false - false | true | false | false - false | true | true | false - true | false | false | false - true | false | true | true - true | true | false | true - true | true | true | true - } -} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/baggage/BaggagePropagatorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/baggage/BaggagePropagatorTest.java new file mode 100644 index 00000000000..5c2f8048e48 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/baggage/BaggagePropagatorTest.java @@ -0,0 +1,261 @@ +package datadog.trace.core.baggage; + +import static datadog.trace.core.baggage.BaggagePropagator.BAGGAGE_KEY; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import datadog.context.Context; +import datadog.context.propagation.CarrierSetter; +import datadog.context.propagation.CarrierVisitor; +import datadog.trace.bootstrap.instrumentation.api.Baggage; +import datadog.trace.bootstrap.instrumentation.api.ContextVisitors; +import datadog.trace.test.util.DDJavaSpecification; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; + +class BaggagePropagatorTest extends DDJavaSpecification { + private static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64; + private static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192; + + private BaggagePropagator propagator; + private CarrierSetter> setter; + private Map carrier; + private Context context; + + static class MapCarrierAccessor + implements CarrierSetter>, CarrierVisitor> { + @Override + public void set(Map carrier, String key, String value) { + if (carrier != null && key != null && value != null) { + carrier.put(key, value); + } + } + + @Override + public void forEachKeyValue(Map carrier, BiConsumer visitor) { + carrier.forEach(visitor); + } + } + + @BeforeEach + void setup() { + this.propagator = + new BaggagePropagator( + true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, DEFAULT_TRACE_BAGGAGE_MAX_BYTES); + this.setter = new MapCarrierAccessor(); + this.carrier = new HashMap<>(); + this.context = Context.root(); + } + + @TableTest({ + "scenario | baggageMap | baggageHeader ", + "three entries | [key1: val1, key2: val2, foo: bar] | 'key1=val1,key2=val2,foo=bar' ", + "special chars are URL encoded | ['\",;\\()/:<=>?@[]{}': '\",;\\'] | '%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C'", + "single entry | [key1: val1] | 'key1=val1' ", + "two entries | [key1: val1, key2: val2] | 'key1=val1,key2=val2' ", + "space is encoded | [serverNode: 'DF 28'] | 'serverNode=DF%2028' ", + "non ASCII value | [userId: Amélie] | 'userId=Am%C3%A9lie' ", + "parenthesis in key | ['user!d(me)': false] | 'user!d%28me%29=false' ", + "non ASCII heart symbol | [abcdefg: 'hijklmnopq♥'] | 'abcdefg=hijklmnopq%E2%99%A5' " + }) + void testBaggagePropagatorContextInjection(Map baggageMap, String baggageHeader) { + this.context = Baggage.create(baggageMap).storeInto(this.context); + + this.propagator.inject(context, carrier, setter); + + assertEquals(baggageHeader, carrier.get(BAGGAGE_KEY)); + } + + @TableTest({ + "scenario | baggage | baggageHeader ", + "limit not reached | [key1: val1, key2: val2] | 'key1=val1,key2=val2'", + "third entry dropped | [key1: val1, key2: val2, key3: val3] | 'key1=val1,key2=val2'" + }) + void testBaggageInjectItemLimit(Map baggage, String baggageHeader) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES); + context = Baggage.create(baggage).storeInto(context); + + this.propagator.inject(context, carrier, setter); + + assertEquals(baggageHeader, carrier.get(BAGGAGE_KEY)); + } + + @TableTest({ + "scenario | baggage | baggageHeader ", + "limit not reached | [key1: val1, key2: val2] | 'key1=val1,key2=val2'", + "third entry exceeds bytes | [key1: val1, key2: val2, key3: val3] | 'key1=val1,key2=val2'", + "single entry exceeds bytes once encoded | [abcdefg: 'hijklmnopq♥'] | '' " + }) + void testBaggageInjectBytesLimit(Map baggage, String baggageHeader) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20); + context = Baggage.create(baggage).storeInto(context); + + this.propagator.inject(context, carrier, setter); + + assertEquals(baggageHeader, carrier.get(BAGGAGE_KEY)); + } + + @TableTest({ + "scenario | baggageHeader | baggageMap ", + "three entries | 'key1=val1,key2=val2,foo=bar' | [key1: val1, key2: val2, foo: bar]", + "URL encoded special chars | '%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C' | ['\",;\\()/:<=>?@[]{}': '\",;\\'] " + }) + void testTracingPropagatorContextExtractor(String baggageHeader, Map baggageMap) { + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + assertEquals(baggageMap, Baggage.fromContext(context).asMap()); + } + + @Test + void testExtractingNonAsciiHeaders() { + Map headers = singletonMap(BAGGAGE_KEY, "key1=vallée,clé2=value"); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + Baggage baggage = Baggage.fromContext(context); + + // non ASCII values data are still accessible as part of the API + assertNotNull(baggage); + assertEquals("vallée", baggage.asMap().get("key1")); + assertEquals("value", baggage.asMap().get("clé2")); + assertNull(baggage.getW3cHeader()); + + this.propagator.inject(Context.root().with(baggage), carrier, setter); + + // baggage are URL encoded if not valid, even if not modified + assertEquals("key1=vall%C3%A9e,cl%C3%A92=value", carrier.get(BAGGAGE_KEY)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "no-equal-sign,foo=gets-dropped-because-previous-pair-is-malformed", + "foo=gets-dropped-because-subsequent-pair-is-malformed,=", + "=no-key", + "no-value=", + "", + ",,", + "=" + }) + void extractInvalidBaggageHeaders(String baggageHeader) { + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + assertNull(Baggage.fromContext(context)); + } + + @TableTest({ + "scenario | baggageHeader | cachedString ", + "valid header is cached | 'key1=val1,key2=val2,foo=bar' | 'key1=val1,key2=val2,foo=bar'", + "invalid chars => no cache | '\";\\()/:<=>?@[]{}=\";\\' | " + }) + void testBaggageCache(String baggageHeader, String cachedString) { + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + Baggage baggageContext = Baggage.fromContext(context); + assertEquals(cachedString, baggageContext.getW3cHeader()); + } + + @TableTest({ + "scenario | baggageHeader | cachedString ", + "limit not reached | 'key1=val1,key2=val2' | 'key1=val1,key2=val2'", + "third entry truncates | 'key1=val1,key2=val2,key3=val3' | 'key1=val1,key2=val2'", + "fourth entry truncates | 'key1=val1,key2=val2,key3=val3,key4=val4' | 'key1=val1,key2=val2'" + }) + void testBaggageCacheItemsLimit(String baggageHeader, String cachedString) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES); + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + Baggage baggageContext = Baggage.fromContext(context); + assertEquals(cachedString, baggageContext.getW3cHeader()); + } + + @TableTest({ + "scenario | baggageHeader | cachedString ", + "limit not reached | 'key1=val1,key2=val2' | 'key1=val1,key2=val2'", + "third entry truncates | 'key1=val1,key2=val2,key3=val3' | 'key1=val1,key2=val2'" + }) + void testBaggageCacheBytesLimit(String scenario, String baggageHeader, String cachedString) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20); + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + Baggage baggageContext = Baggage.fromContext(context); + assertEquals(cachedString, baggageContext.getW3cHeader()); + } + + @TableTest({ + "scenario | baggageHeader | baggageMap ", + "single entry | 'key1=val1' | [key1: val1] ", + "two entries | 'key1=val1,key2=val2' | [key1: val1, key2: val2]", + "third entry dropped | 'key1=val1,key2=val2,key3=val3' | [key1: val1, key2: val2]" + }) + void testBaggageExtractItemsLimit(String baggageHeader, Map baggageMap) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES); + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + // parsing stops once the item limit is exceeded + assertEquals(baggageMap, Baggage.fromContext(context).asMap()); + } + + @TableTest({ + "scenario | baggageHeader | baggageMap ", + "single entry | 'key1=val1' | [key1: val1] ", + "two entries | 'key1=val1,key2=val2' | [key1: val1, key2: val2]", + "third entry dropped | 'key1=val1,key2=val2,key3=val3' | [key1: val1, key2: val2]" + }) + void testBaggageExtractBytesLimit(String baggageHeader, Map baggageMap) { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20); + Map headers = singletonMap(BAGGAGE_KEY, baggageHeader); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + // parsing stops once the byte limit is exceeded + assertEquals(baggageMap, Baggage.fromContext(context).asMap()); + } + + @Test + void testBaggageExtract0ItemLimit() { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, 0, DEFAULT_TRACE_BAGGAGE_MAX_BYTES); + Map headers = singletonMap(BAGGAGE_KEY, "key1=value1"); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + assertNull(Baggage.fromContext(context)); + } + + @Test + void testBaggageExtract0ByteLimit() { + // creating a new instance after injecting config + propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 0); + Map headers = singletonMap(BAGGAGE_KEY, "key1=value1"); + + context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap()); + + assertNull(Baggage.fromContext(context)); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.java b/dd-trace-core/src/test/java/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.java new file mode 100644 index 00000000000..68779317248 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/servicediscovery/ServiceDiscoveryTest.java @@ -0,0 +1,71 @@ +package datadog.trace.core.servicediscovery; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.msgpack.core.MessagePack; +import org.msgpack.value.MapValue; + +@Timeout(value = 10, unit = TimeUnit.SECONDS) +class ServiceDiscoveryTest { + @Test + void encodePayloadWithAllOptionalFields() throws IOException { + String tracerVersion = "1.2.3"; + String hostname = "test-host"; + String runtimeID = "rid-123"; + String service = "orders"; + String env = "prod"; + String serviceVersion = "1.1.1"; + UTF8BytesString processTags = UTF8BytesString.create("key1:val1,key2:val2"); + String containerID = "containerID"; + boolean appLogsCollectionEnabled = true; + + byte[] out = + ServiceDiscovery.encodePayload( + tracerVersion, + hostname, + appLogsCollectionEnabled, + runtimeID, + service, + env, + serviceVersion, + processTags, + containerID); + MapValue map = MessagePack.newDefaultUnpacker(out).unpackValue().asMapValue(); + + assertEquals(11, map.size()); + assertEquals( + "{\"schema_version\":2,\"tracer_language\":\"java\",\"tracer_version\":\"1.2.3\",\"hostname\":\"test-host\",\"logs_collected\":true,\"runtime_id\":\"rid-123\",\"service_name\":\"orders\",\"service_env\":\"prod\",\"service_version\":\"1.1.1\",\"process_tags\":\"key1:val1,key2:val2\",\"container_id\":\"containerID\"}", + map.toString()); + } + + @Test + void encodePayloadOnlyRequiredFields() throws IOException { + String tracerVersion = "1.2.3"; + String hostname = "my_host"; + + byte[] out = + ServiceDiscovery.encodePayload( + tracerVersion, hostname, false, null, null, null, null, null, null); + MapValue map = MessagePack.newDefaultUnpacker(out).unpackValue().asMapValue(); + + assertEquals(5, map.size()); + assertEquals( + "{\"schema_version\":2,\"tracer_language\":\"java\",\"tracer_version\":\"1.2.3\",\"hostname\":\"my_host\",\"logs_collected\":false}", + map.toString()); + } + + @Test + void generateFileName() { + String name = ServiceDiscovery.generateFileName(); + + String prefix = "datadog-tracer-info-"; + assertTrue(name.startsWith(prefix)); + assertEquals(prefix.length() + 8, name.length()); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java new file mode 100644 index 00000000000..42a8b80e054 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java @@ -0,0 +1,702 @@ +package datadog.trace.core.taginterceptor; + +import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE; +import static datadog.trace.api.config.TracerConfig.SPLIT_BY_TAGS; +import static datadog.trace.junit.utils.config.WithConfigExtension.injectSysConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import datadog.trace.api.DDSpanTypes; +import datadog.trace.api.DDTags; +import datadog.trace.api.ProductTraceSource; +import datadog.trace.api.remoteconfig.ServiceNameCollector; +import datadog.trace.api.remoteconfig.ServiceNameCollectorTestBridge; +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.common.sampling.AllSampler; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.common.writer.LoggingWriter; +import datadog.trace.core.CoreSpan; +import datadog.trace.core.CoreTracer; +import datadog.trace.core.DDCoreJavaSpecification; +import datadog.trace.core.DDSpanContext; +import datadog.trace.junit.utils.config.WithConfig; +import datadog.trace.junit.utils.tabletest.ConfigDefaultsConverter; +import datadog.trace.junit.utils.tabletest.DDTagsConverter; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; + +@WithConfig(key = SPLIT_BY_TAGS, value = "sn.tag1,sn.tag2") +class TagInterceptorTest extends DDCoreJavaSpecification { + + @TableTest({ + "scenario | tag | name | expected ", + "service.name some | 'DDTags.SERVICE_NAME' | 'some-service' | 'new-service' ", + "service.name other | 'DDTags.SERVICE_NAME' | 'other-service' | 'other-service'", + "service some | 'service' | 'some-service' | 'new-service' ", + "service other | 'service' | 'other-service' | 'other-service'", + "peer.service some | 'Tags.PEER_SERVICE' | 'some-service' | 'new-service' ", + "peer.service other | 'Tags.PEER_SERVICE' | 'other-service' | 'other-service'", + "sn.tag1 some | 'sn.tag1' | 'some-service' | 'new-service' ", + "sn.tag1 other | 'sn.tag1' | 'other-service' | 'other-service'", + "sn.tag2 some | 'sn.tag2' | 'some-service' | 'new-service' ", + "sn.tag2 other | 'sn.tag2' | 'other-service' | 'other-service'" + }) + @WithConfig(key = "dd.trace.PeerServiceTagInterceptor.enabled", value = "true", addPrefix = false) + void setServiceName( + @ConvertWith(DDTagsConverter.class) String tag, String name, String expected) { + Map mapping = Collections.singletonMap("some-service", "new-service"); + CoreTracer tracer = + tracerBuilder() + .serviceName("wrong-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .serviceNameMappings(mapping) + .build(); + + AgentSpan span = tracer.buildSpan("datadog", "some span").withTag(tag, name).start(); + span.finish(); + + assertEquals(expected, span.getServiceName()); + } + + @TableTest({ + "scenario | serviceName | expected | mapping ", + "default service / no match | 'DEFAULT_SERVICE_NAME' | 'DEFAULT_SERVICE_NAME' | [other-service-name: other-service] ", + "default service / match | 'DEFAULT_SERVICE_NAME' | 'new-service' | ['DEFAULT_SERVICE_NAME': new-service]", + "custom service / match | 'other-service-name' | 'other-service' | [other-service-name: other-service] " + }) + void defaultOrConfiguredServiceNameCanBeRemappedWithoutSettingTag( + @ConvertWith(ConfigDefaultsConverter.class) String serviceName, + @ConvertWith(ConfigDefaultsConverter.class) String expected, + @ConvertWith(ConfigDefaultsConverter.class) Map mapping) { + CoreTracer tracer = + tracerBuilder() + .serviceName(serviceName) + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .serviceNameMappings(mapping) + .build(); + AgentSpan span = tracer.buildSpan("datadog", "some span").start(); + span.finish(); + + assertEquals(expected, span.getServiceName()); + } + + @TableTest({ + "scenario | context | serviceName | expected ", + "root context with default service | '/' | 'DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME' | 'DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME'", + "empty context with default service | '' | 'DEFAULT_SERVICE_NAME' | 'DEFAULT_SERVICE_NAME' ", + "/some-context with default service | '/some-context' | 'DEFAULT_SERVICE_NAME' | 'some-context' ", + "other-context with default service | 'other-context' | 'DEFAULT_SERVICE_NAME' | 'other-context' ", + "root context with my-service | '/' | 'my-service' | 'my-service' ", + "empty context with my-service | '' | 'my-service' | 'my-service' ", + "/some-context with my-service | '/some-context' | 'my-service' | 'my-service' ", + "other-context with my-service | 'other-context' | 'my-service' | 'my-service' " + }) + void setServiceNameFromServletContext( + String context, + @ConvertWith(ConfigDefaultsConverter.class) String serviceName, + @ConvertWith(ConfigDefaultsConverter.class) String expected) { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + span.setTag(DDTags.SERVICE_NAME, serviceName); + span.setTag("servlet.context", context); + + assertEquals(expected, span.getServiceName()); + } + + @TableTest({ + "scenario | context | serviceName ", + "root context / default service | '/' | 'DEFAULT_SERVICE_NAME'", + "empty context / default service | '' | 'DEFAULT_SERVICE_NAME'", + "/some-context / default service | '/some-context' | 'DEFAULT_SERVICE_NAME'", + "other-context / default service | 'other-context' | 'DEFAULT_SERVICE_NAME'", + "root context / env service | '/' | 'ENV_SERVICE_NAME' ", + "empty context / env service | '' | 'ENV_SERVICE_NAME' ", + "/some-context / env service | '/some-context' | 'ENV_SERVICE_NAME' ", + "other-context / env service | 'other-context' | 'ENV_SERVICE_NAME' ", + "root context / my-service | '/' | 'my-service' ", + "empty context / my-service | '' | 'my-service' ", + "/some-context / my-service | '/some-context' | 'my-service' ", + "other-context / my-service | 'other-context' | 'my-service' " + }) + void settingServiceNameAsPropertyDisablesServletContext( + String context, @ConvertWith(ConfigDefaultsConverter.class) String serviceName) { + injectSysConfig("service", serviceName); + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + span.setTag("servlet.context", context); + + assertEquals(serviceName, span.getServiceName()); + } + + @TableTest({ + "scenario | context | serviceName | mapping ", + "default service | '/some-context' | 'DEFAULT_SERVICE_NAME' | [DEFAULT_SERVICE_NAME: new-service]", + "my-service | '/some-context' | 'my-service' | [my-service: new-service] " + }) + void mappingCausesServletContextToNotChangeServiceName( + String context, + @ConvertWith(ConfigDefaultsConverter.class) String serviceName, + @ConvertWith(ConfigDefaultsConverter.class) Map mapping) { + CoreTracer tracer = + tracerBuilder() + .serviceName(serviceName) + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .serviceNameMappings(mapping) + .build(); + + AgentSpan span = tracer.buildSpan("datadog", "some span").start(); + span.setTag("servlet.context", context); + span.finish(); + + assertEquals("new-service", span.getServiceName()); + } + + private CoreTracer createSplittingTracer(String tag) { + return tracerBuilder() + .serviceName("my-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + // equivalent to split-by-tags: tag + .tagInterceptor( + new TagInterceptor( + true, "my-service", Collections.singleton(tag), new RuleFlags(), false)) + .build(); + } + + @TableTest({ + "scenario | expected | jeeActive", + "jee inactive | 'some-context' | false ", + "jee active | 'my-service' | true " + }) + void splitByTagsForServletContextAndExperimentalJeeSplitByDeployment( + String expected, boolean jeeActive) { + CoreTracer tracer = + tracerBuilder() + .serviceName("my-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .tagInterceptor( + new TagInterceptor( + false, "my-service", Collections.emptySet(), new RuleFlags(), jeeActive)) + .build(); + + AgentSpan span = tracer.buildSpan("datadog", "some span").start(); + span.setTag(InstrumentationTags.SERVLET_CONTEXT, "some-context"); + span.finish(); + + assertEquals(expected, span.getServiceName()); + } + + @Test + void peerServiceThenSplitByTagsViaBuilder() { + CoreTracer tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION); + + AgentSpan span = + tracer + .buildSpan("datadog", "some span") + .withTag(Tags.PEER_SERVICE, "peer-service") + .withTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") + .start(); + span.finish(); + + assertEquals("some-queue", span.getServiceName()); + } + + @Test + void peerServiceThenSplitByTagsViaSetTag() { + CoreTracer tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION); + + AgentSpan span = tracer.buildSpan("datadog", "some span").start(); + span.setTag(Tags.PEER_SERVICE, "peer-service"); + span.setTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue"); + span.finish(); + + assertEquals("some-queue", span.getServiceName()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void splitByTagsThenPeerServiceViaBuilder(boolean enabled) { + injectSysConfig("dd.trace.PeerServiceTagInterceptor.enabled", String.valueOf(enabled), false); + CoreTracer tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION); + + AgentSpan span = + tracer + .buildSpan("datadog", "some span") + .withTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue") + .withTag(Tags.PEER_SERVICE, "peer-service") + .start(); + span.finish(); + + assertEquals(enabled, "peer-service".equals(span.getServiceName())); + } + + @Test + @WithConfig(key = "dd.trace.PeerServiceTagInterceptor.enabled", value = "true", addPrefix = false) + void splitByTagsThenPeerServiceViaSetTag() { + CoreTracer tracer = createSplittingTracer(Tags.MESSAGE_BUS_DESTINATION); + + AgentSpan span = tracer.buildSpan("datadog", "some span").start(); + span.setTag(Tags.MESSAGE_BUS_DESTINATION, "some-queue"); + span.setTag(Tags.PEER_SERVICE, "peer-service"); + span.finish(); + + assertEquals("peer-service", span.getServiceName()); + } + + @Test + void setResourceName() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + + String name = "my resource name"; + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + span.setTag(DDTags.RESOURCE_NAME, name); + span.finish(); + writer.waitForTraces(1); + + assertEquals(name, span.getResourceName().toString()); + } + + @Test + void setResourceNameIgnoresNull() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + + AgentSpan span = tracer.buildSpan("datadog", "test").withResourceName("keep").start(); + span.setTag(DDTags.RESOURCE_NAME, (String) null); + span.finish(); + writer.waitForTraces(1); + + assertEquals("keep", span.getResourceName().toString()); + } + + @Test + void setSpanType() { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + String type = DDSpanTypes.HTTP_CLIENT; + span.setSpanType(type); + span.finish(); + + assertEquals(type, span.getSpanType()); + } + + @Test + void setSpanTypeWithTag() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + String type = DDSpanTypes.HTTP_CLIENT; + span.setTag(DDTags.SPAN_TYPE, type); + span.finish(); + writer.waitForTraces(1); + + assertEquals(type, span.getSpanType()); + } + + static Stream spanMetricsStartsEmptyButAddedWithRateLimitingValueArguments() { + return Stream.of( + arguments("int 0", 0, 0), + arguments("int 1", 1, 1), + arguments("float 0", 0f, 0f), + arguments("float 1", 1f, 1f), + arguments("double 0.1", 0.1, 0.1), + arguments("double 1.1", 1.1, 1.1), + arguments("int -1", -1, -1), + arguments("int 10", 10, 10), + arguments("string '00'", "00", 0.0), + arguments("string '1'", "1", 1.0), + arguments("string '1.0'", "1.0", 1.0), + arguments("string '0'", "0", 0.0), + arguments("string '0.1'", "0.1", 0.1), + arguments("string '1.1'", "1.1", 1.1), + arguments("string '-1'", "-1", -1.0), + arguments("string 'str'", "str", null)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("spanMetricsStartsEmptyButAddedWithRateLimitingValueArguments") + void spanMetricsStartsEmptyButAddedWithRateLimitingValue( + String scenario, Object rate, Object result) throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + + assertNull(span.getTag(ANALYTICS_SAMPLE_RATE)); + + span.setTag(ANALYTICS_SAMPLE_RATE, rate); + span.finish(); + writer.waitForTraces(1); + + assertEquals(result, span.getTag(ANALYTICS_SAMPLE_RATE)); + } + + static Stream setPrioritySamplingViaTagArguments() { + return Stream.of( + arguments("manual.keep / true", DDTags.MANUAL_KEEP, true, (int) PrioritySampling.USER_KEEP), + arguments("manual.keep / false", DDTags.MANUAL_KEEP, false, null), + arguments( + "manual.keep / 'true'", DDTags.MANUAL_KEEP, "true", (int) PrioritySampling.USER_KEEP), + arguments("manual.keep / 'false'", DDTags.MANUAL_KEEP, "false", null), + arguments("manual.keep / 'asdf'", DDTags.MANUAL_KEEP, "asdf", null), + arguments("manual.drop / true", DDTags.MANUAL_DROP, true, (int) PrioritySampling.USER_DROP), + arguments("manual.drop / false", DDTags.MANUAL_DROP, false, null), + arguments( + "manual.drop / 'true'", DDTags.MANUAL_DROP, "true", (int) PrioritySampling.USER_DROP), + arguments("manual.drop / 'false'", DDTags.MANUAL_DROP, "false", null), + arguments("manual.drop / 'asdf'", DDTags.MANUAL_DROP, "asdf", null), + arguments("asm.keep / true", Tags.ASM_KEEP, true, (int) PrioritySampling.USER_KEEP), + arguments("asm.keep / false", Tags.ASM_KEEP, false, null), + arguments("asm.keep / 'true'", Tags.ASM_KEEP, "true", (int) PrioritySampling.USER_KEEP), + arguments("asm.keep / 'false'", Tags.ASM_KEEP, "false", null), + arguments("asm.keep / 'asdf'", Tags.ASM_KEEP, "asdf", null), + arguments( + "sampling.priority / -1", Tags.SAMPLING_PRIORITY, -1, (int) PrioritySampling.USER_DROP), + arguments( + "sampling.priority / 0", Tags.SAMPLING_PRIORITY, 0, (int) PrioritySampling.USER_DROP), + arguments( + "sampling.priority / 1", Tags.SAMPLING_PRIORITY, 1, (int) PrioritySampling.USER_KEEP), + arguments( + "sampling.priority / 2", Tags.SAMPLING_PRIORITY, 2, (int) PrioritySampling.USER_KEEP), + arguments( + "sampling.priority / '-1'", + Tags.SAMPLING_PRIORITY, + "-1", + (int) PrioritySampling.USER_DROP), + arguments( + "sampling.priority / '0'", + Tags.SAMPLING_PRIORITY, + "0", + (int) PrioritySampling.USER_DROP), + arguments( + "sampling.priority / '1'", + Tags.SAMPLING_PRIORITY, + "1", + (int) PrioritySampling.USER_KEEP), + arguments( + "sampling.priority / '2'", + Tags.SAMPLING_PRIORITY, + "2", + (int) PrioritySampling.USER_KEEP), + arguments("sampling.priority / 'asdf'", Tags.SAMPLING_PRIORITY, "asdf", null)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("setPrioritySamplingViaTagArguments") + void setPrioritySamplingViaTag(String scenario, String tag, Object value, Integer expected) { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + span.setTag(tag, value); + + assertEquals(expected, span.getSamplingPriority()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void setErrorFlagWhenErrorTagReported(boolean error) throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + span.setTag(Tags.ERROR, error); + span.finish(); + writer.waitForTraces(1); + + assertEquals(error, span.isError()); + } + + static Stream interceptorsApplyToBuilderTooArguments() { + return Stream.of( + arguments( + "serviceName", + DDTags.SERVICE_NAME, + "my-service", + (Function) DDSpanContext::getServiceName), + arguments( + "resourceName", + DDTags.RESOURCE_NAME, + "my-resource", + (Function) ctx -> ctx.getResourceName().toString()), + arguments( + "spanType", + DDTags.SPAN_TYPE, + "my-span-type", + (Function) DDSpanContext::getSpanType)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("interceptorsApplyToBuilderTooArguments") + void interceptorsApplyToBuilderToo( + String attribute, String name, String value, Function getter) + throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + + AgentSpan span = tracer.buildSpan("datadog", "interceptor.test").withTag(name, value).start(); + span.finish(); + writer.waitForTraces(1); + + assertEquals(value, getter.apply((DDSpanContext) span.context())); + } + + @Test + void decoratorsApplyToBuilderToo() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + + AgentSpan span = + tracer.buildSpan("datadog", "decorator.test").withTag("sn.tag1", "some val").start(); + span.finish(); + writer.waitForTraces(1); + assertEquals("some val", span.getServiceName()); + + span = + tracer + .buildSpan("datadog", "decorator.test") + .withTag("servlet.context", "/my-servlet") + .start(); + assertEquals("my-servlet", span.getServiceName()); + + span = tracer.buildSpan("datadog", "decorator.test").withTag("error", "true").start(); + span.finish(); + writer.waitForTraces(2); + assertTrue(span.isError()); + + span = + tracer + .buildSpan("datadog", "decorator.test") + .withTag(Tags.DB_STATEMENT, "some-statement") + .start(); + span.finish(); + writer.waitForTraces(3); + assertEquals("some-statement", span.getResourceName().toString()); + } + + @TableTest({ + "scenario | decorator | enabled", + "lowercase enabled | 'servicenametaginterceptor' | true ", + "camelCase enabled | 'ServiceNameTagInterceptor' | true ", + "lowercase disabled | 'servicenametaginterceptor' | false ", + "camelCase disabled | 'ServiceNameTagInterceptor' | false " + }) + void disableDecoratorViaConfig(String decorator, boolean enabled) { + injectSysConfig("dd.trace." + decorator + ".enabled", String.valueOf(enabled), false); + + CoreTracer tracer = + tracerBuilder() + .serviceName("some-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .build(); + + AgentSpan span = + tracer + .buildSpan("datadog", "some span") + .withTag(DDTags.SERVICE_NAME, "other-service") + .start(); + span.finish(); + + assertEquals(enabled ? "other-service" : "some-service", span.getServiceName()); + } + + @TableTest({ + "scenario | tag | name | expected ", + "service.name | 'DDTags.SERVICE_NAME' | 'new-service' | 'some-service'", + "service | 'service' | 'new-service' | 'some-service'", + "sn.tag1 | 'sn.tag1' | 'new-service' | 'new-service' " + }) + void disablingServiceDecoratorDoesNotDisableSplitByTags( + @ConvertWith(DDTagsConverter.class) String tag, String name, String expected) { + injectSysConfig("dd.trace.ServiceNameTagInterceptor.enabled", "false", false); + + CoreTracer tracer = + tracerBuilder() + .serviceName("some-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .build(); + + AgentSpan span = tracer.buildSpan("datadog", "some span").withTag(tag, name).start(); + span.finish(); + + assertEquals(expected, span.getServiceName()); + } + + @Test + void changeTopLevelStatusWhenChangingServiceName() { + CoreTracer tracer = + tracerBuilder() + .serviceName("some-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .build(); + + AgentSpan parent = tracer.buildSpan("datadog", "parent").withServiceName("parent").start(); + + // the service name doesn't match the parent + AgentSpan child = + tracer.buildSpan("datadog", "child").withServiceName("child").asChildOf(parent).start(); + assertTrue(((CoreSpan) child).isTopLevel()); + + // the service name is changed to match the parent + child.setTag(DDTags.SERVICE_NAME, "parent"); + assertFalse(((CoreSpan) child).isTopLevel()); + + // the service name is changed to no longer match the parent + child.setTag(DDTags.SERVICE_NAME, "foo"); + assertTrue(((CoreSpan) child).isTopLevel()); + } + + static Stream treat1ValueAsTrueForBooleanTagValuesArguments() { + return Stream.of( + arguments("manual.drop / true", DDTags.MANUAL_DROP, true, (int) PrioritySampling.USER_DROP), + arguments("manual.drop / '1'", DDTags.MANUAL_DROP, "1", (int) PrioritySampling.USER_DROP), + arguments("manual.drop / false", DDTags.MANUAL_DROP, false, null), + arguments("manual.drop / '0'", DDTags.MANUAL_DROP, "0", null), + arguments("manual.keep / true", DDTags.MANUAL_KEEP, true, (int) PrioritySampling.USER_KEEP), + arguments("manual.keep / '1'", DDTags.MANUAL_KEEP, "1", (int) PrioritySampling.USER_KEEP), + arguments("manual.keep / false", DDTags.MANUAL_KEEP, false, null), + arguments("manual.keep / '0'", DDTags.MANUAL_KEEP, "0", null)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("treat1ValueAsTrueForBooleanTagValuesArguments") + void treat1ValueAsTrueForBooleanTagValues( + String scenario, String tag, Object value, Integer samplingPriority) { + CoreTracer tracer = + tracerBuilder() + .serviceName("some-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .build(); + + AgentSpan span = tracer.buildSpan("datadog", "test").start(); + assertNull(span.getSamplingPriority()); + + span.setTag(tag, value); + assertEquals(samplingPriority, span.getSamplingPriority()); + } + + @TableTest({ + "scenario | value | resourceName | meta ", + "null url | | 'fakeOperation' | [:] ", + "space url | ' ' | '/' | [:] ", + "tab url | '\t' | '/' | [:] ", + "simple path | '/path' | '/path' | [:] ", + "complex path | '/ABC/a-1/b_2/c.3/d4d/5f/6' | '/ABC/?/?/?/?/?/?' | [:] ", + "not found | '/not-found' | '404' | [Tags.HTTP_STATUS: 404] ", + "with method | '/with-method' | 'POST /with-method' | [Tags.HTTP_METHOD: Post]" + }) + void urlAsResourceNameRuleSetsTheResourceName( + String value, + String resourceName, + @ConvertWith(DDTagsConverter.class) Map meta) { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + + AgentSpan span = tracer.buildSpan("datadog", "fakeOperation").start(); + for (Map.Entry entry : meta.entrySet()) { + span.setTag(entry.getKey(), entry.getValue()); + } + + span.setTag(Tags.HTTP_URL, value); + + try { + assertEquals(resourceName, span.getResourceName().toString()); + } finally { + span.finish(); + } + } + + @Test + void whenUserSetsPeerServiceTheSourceShouldBePeerService() { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + + AgentSpan span = tracer.buildSpan("datadog", "fakeOperation").start(); + try { + span.setTag(Tags.PEER_SERVICE, "test"); + assertEquals("peer.service", span.getTag(DDTags.PEER_SERVICE_SOURCE)); + } finally { + span.finish(); + } + } + + @Test + void whenInterceptServiceNameExtraServiceProviderIsCalled() { + ServiceNameCollector origServiceNameCollector = ServiceNameCollector.get(); + ServiceNameCollector extraServiceProvider = mock(ServiceNameCollector.class); + ServiceNameCollectorTestBridge.setInstance(extraServiceProvider); + try { + RuleFlags ruleFlags = mock(RuleFlags.class); + when(ruleFlags.isEnabled(any())).thenReturn(true); + TagInterceptor interceptor = + new TagInterceptor( + true, "my-service", Collections.singleton(DDTags.SERVICE_NAME), ruleFlags, false); + + interceptor.interceptServiceName(null, mock(DDSpanContext.class), "some-service"); + + verify(extraServiceProvider, times(1)).addService("some-service"); + } finally { + ServiceNameCollectorTestBridge.setInstance(origServiceNameCollector); + } + } + + @TableTest({ + "scenario | value | expected ", + "root context | '/' | 'root-servlet'", + "/test path | '/test' | 'test' ", + "test path | 'test' | 'test' " + }) + void whenInterceptServletContextExtraServiceProviderIsCalled(String value, String expected) { + ServiceNameCollector origServiceNameCollector = ServiceNameCollector.get(); + ServiceNameCollector extraServiceProvider = mock(ServiceNameCollector.class); + ServiceNameCollectorTestBridge.setInstance(extraServiceProvider); + try { + RuleFlags ruleFlags = mock(RuleFlags.class); + when(ruleFlags.isEnabled(any())).thenReturn(true); + TagInterceptor interceptor = + new TagInterceptor( + true, "my-service", Collections.singleton("servlet.context"), ruleFlags, false); + + interceptor.interceptServletContext(mock(DDSpanContext.class), value); + + verify(extraServiceProvider, times(1)).addService(expected); + } finally { + ServiceNameCollectorTestBridge.setInstance(origServiceNameCollector); + } + } + + @Test + void whenInterceptsProductTraceSourcePropagationTagUpdatePropagatedTraceSourceIsCalled() { + RuleFlags ruleFlags = mock(RuleFlags.class); + when(ruleFlags.isEnabled(any())).thenReturn(true); + TagInterceptor interceptor = new TagInterceptor(ruleFlags); + DDSpanContext context = mock(DDSpanContext.class); + + interceptor.interceptTag(context, Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + + verify(context, times(1)).addPropagatedTraceSource(ProductTraceSource.ASM); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.java new file mode 100644 index 00000000000..87facab95fe --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/traceinterceptor/LatencyTraceInterceptorTest.java @@ -0,0 +1,55 @@ +package datadog.trace.core.traceinterceptor; + +import static datadog.trace.junit.utils.config.WithConfigExtension.injectSysConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.CoreTracer; +import datadog.trace.core.DDCoreJavaSpecification; +import datadog.trace.core.DDSpan; +import datadog.trace.junit.utils.tabletest.DDTagsConverter; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.converter.ConvertWith; +import org.tabletest.junit.TableTest; + +@Timeout(value = 10, unit = TimeUnit.SECONDS) +class LatencyTraceInterceptorTest extends DDCoreJavaSpecification { + + @TableTest({ + "scenario | partialFlushEnabled | latencyThreshold | priorityTag | minDuration | expected", + "partial flush / keep / under threshold | 'true' | '200' | 'DDTags.MANUAL_KEEP' | 10 | 2 ", + "partial flush / drop / under threshold | 'true' | '200' | 'DDTags.MANUAL_DROP' | 10 | -1 ", + "partial flush / keep / over threshold | 'true' | '200' | 'DDTags.MANUAL_KEEP' | 300 | 2 ", + "partial flush / drop / over threshold | 'true' | '200' | 'DDTags.MANUAL_DROP' | 300 | -1 ", + "no partial flush / keep / under threshold | 'false' | '200' | 'DDTags.MANUAL_KEEP' | 10 | 2 ", + "no partial flush / drop / under threshold | 'false' | '200' | 'DDTags.MANUAL_DROP' | 10 | -1 ", + "no partial flush / keep / over threshold | 'false' | '200' | 'DDTags.MANUAL_KEEP' | 300 | 2 ", + "no partial flush / drop / over threshold | 'false' | '200' | 'DDTags.MANUAL_DROP' | 300 | 2 " + }) + void testSetSamplingPriorityAccordingToLatency( + String partialFlushEnabled, + String latencyThreshold, + @ConvertWith(DDTagsConverter.class) String priorityTag, + long minDuration, + int expected) + throws InterruptedException { + injectSysConfig("trace.partial.flush.enabled", partialFlushEnabled); + injectSysConfig("trace.experimental.keep.latency.threshold.ms", latencyThreshold); + + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + + AgentSpan spanSetup = + tracer.buildSpan("test", "my_operation_name").withTag(priorityTag, true).start(); + Thread.sleep(minDuration); + spanSetup.finish(); + + List trace = writer.firstTrace(); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals(expected, span.context().getSamplingPriority()); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/util/GlobPatternTest.java b/dd-trace-core/src/test/java/datadog/trace/core/util/GlobPatternTest.java new file mode 100644 index 00000000000..19c92700940 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/util/GlobPatternTest.java @@ -0,0 +1,23 @@ +package datadog.trace.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.tabletest.junit.TableTest; + +class GlobPatternTest { + + @TableTest({ + "scenario | globPattern | expectedRegex", + "star alone | '*' | '^.*$' ", + "question alone | '?' | '^.$' ", + "two questions | '??' | '^..$' ", + "prefix star | 'Foo*' | '^Foo.*$' ", + "literal abc | 'abc' | '^abc$' ", + "question alone (dup) | '?' | '^.$' ", + "single char | 'F?o' | '^F.o$' ", + "Bar prefix | 'Bar*' | '^Bar.*$' " + }) + void convertGlobPatternToRegex(String globPattern, String expectedRegex) { + assertEquals(expectedRegex, GlobPattern.globToRegex(globPattern)); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/util/LRUCacheTest.java b/dd-trace-core/src/test/java/datadog/trace/core/util/LRUCacheTest.java new file mode 100644 index 00000000000..43daa3620ce --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/util/LRUCacheTest.java @@ -0,0 +1,48 @@ +package datadog.trace.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class LRUCacheTest { + + @Test + void shouldEjectLeastRecentlyUsedElement() { + LRUCache lruCache = new LRUCache<>(5); + for (int i = 1; i <= 5; i++) { + lruCache.put(i, String.valueOf(i)); + } + // now look at 2 values + lruCache.get(1); + lruCache.get(3); + // now insert 2 new values + lruCache.put(6, "6"); + lruCache.put(7, "7"); + + assertEquals(5, lruCache.size()); + assertTrue(lruCache.values().containsAll(Arrays.asList("1", "3", "5", "6", "7"))); + } + + @Test + void shouldNotifyListenerWhenEjectingLeastRecentlyUsedElement() { + List ejected = new ArrayList<>(); + LRUCache.ExpiryListener listener = entry -> ejected.add(entry.getKey()); + + LRUCache lruCache = new LRUCache<>(listener, 10, 0.75f, 5); + for (int i = 1; i <= 5; i++) { + lruCache.put(i, String.valueOf(i)); + } + // now look at 2 values + lruCache.get(1); + lruCache.get(3); + // now insert 2 new values + lruCache.put(6, "6"); + lruCache.put(7, "7"); + + assertEquals(Arrays.asList(2, 4), ejected); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/util/MatchersTest.java b/dd-trace-core/src/test/java/datadog/trace/core/util/MatchersTest.java new file mode 100644 index 00000000000..3eaea551323 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/util/MatchersTest.java @@ -0,0 +1,116 @@ +package datadog.trace.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class MatchersTest { + + @ParameterizedTest + @NullSource + @ValueSource(strings = {"*", "**"}) + void matchAllScenariosMustReturnAnAnyMatcher(String glob) { + assertInstanceOf(Matchers.AnyMatcher.class, Matchers.compileGlob(glob)); + } + + @ParameterizedTest + @ValueSource(strings = {"a", "ogre", "bcoho34e2"}) + void patternWithoutStarOrQuestionMustBeAnEqualsMatcher(String glob) { + assertInstanceOf(Matchers.InsensitiveEqualsMatcher.class, Matchers.compileGlob(glob)); + } + + @ParameterizedTest + @ValueSource(strings = {"?", "foo*", "*bar", "F?oB?r", "F?o*", "?*", "*?"}) + void patternWithEitherStarOrQuestionMustBeAPatternMatcher(String glob) { + assertInstanceOf(Matchers.PatternMatcher.class, Matchers.compileGlob(glob)); + } + + @ParameterizedTest + @ValueSource(strings = {"", "a", "abc", "cde"}) + void anExactMatcherIsSelfMatching(String pattern) { + assertTrue(Matchers.compileGlob(pattern).matches(pattern)); + } + + static Stream aPatternMatcherTestArguments() { + return Stream.of( + arguments("fo? matches Foo", "fo?", "Foo", true), + arguments("Fo? matches Foo", "Fo?", "Foo", true), + arguments("Fo? matches StringBuilder Foo", "Fo?", new StringBuilder("Foo"), true), + arguments("Fo? matches StringBuilder foo", "Fo?", new StringBuilder("foo"), true), + arguments("Foo matches StringBuilder foo", "Foo", new StringBuilder("foo"), true), + arguments("bar does not match StringBuilder Baz", "bar", new StringBuilder("Baz"), false), + arguments("Fo? does not match Fooo", "Fo?", "Fooo", false), + arguments("Fo* matches Fo", "Fo*", "Fo", true), + arguments("Fo* does not match Fa", "Fo*", "Fa", false), + arguments("F*B?r matches FooBar", "F*B?r", "FooBar", true), + arguments("F*B?r does not match FooFar", "F*B?r", "FooFar", false), + arguments("f*b?r matches FooBar", "f*b?r", "FooBar", true), + arguments("* matches true", "*", true, true), + arguments("true matches true", "true", true, true), + arguments("false matches false", "false", false, true), + arguments("TRUE matches true", "TRUE", true, true), + arguments("FALSE matches false", "FALSE", false, true), + arguments("True matches true", "True", true, true), + arguments("False matches false", "False", false, true), + arguments("T* matches true", "T*", true, true), + arguments("F* matches false", "F*", false, true), + arguments("empty matches empty", "", "", true), + arguments("empty does not match non-empty", "", "non-empty", false), + arguments("* matches foo", "*", "foo", true), + arguments("** matches foo", "**", "foo", true), + arguments("??? matches foo", "???", "foo", true), + arguments("* matches int 20", "*", 20, true), + arguments("20 matches int 20", "20", 20, true), + arguments("-20 matches int -20", "-20", -20, true), + arguments("* matches byte 20", "*", (byte) 20, true), + arguments("20 matches byte 20", "20", (byte) 20, true), + arguments("* matches short 20", "*", (short) 20, true), + arguments("20 matches short 20", "20", (short) 20, true), + arguments("* matches long 20", "*", 20L, true), + arguments("20 matches long 20", "20", 20L, true), + arguments("* matches float 20", "*", 20F, true), + arguments("20 matches float 20", "20", 20F, true), + arguments("* matches double 20", "*", 20D, true), + arguments("20 matches double 20", "20", 20D, true), + arguments("20 matches BigInteger 20", "20", new BigInteger("20"), true), + arguments("20 matches BigDecimal 20", "20", new BigDecimal("20"), true), + arguments("2* does not match float 20.1", "2*", 20.1F, false), + arguments("2* does not match double 20.1", "2*", 20.1D, false), + arguments("2* does not match BigDecimal 20.1", "2*", new BigDecimal("20.1"), false), + arguments("* matches arbitrary Object", "*", new Object(), true), + arguments("** matches arbitrary Object", "**", new Object(), true), + arguments("? does not match arbitrary Object", "?", new Object(), false), + arguments("* matches null", "*", null, true), + arguments("? does not match null", "?", null, false), + arguments("[a-z] matches [a-z]", "[a-z]", "[a-z]", true), + arguments("[a-z] does not match a", "[a-z]", "a", false), + arguments("[abc] matches [abc]", "[abc]", "[abc]", true), + arguments("[AbC] matches [abc]", "[AbC]", "[abc]", true), + arguments("[Ab] matches StringBuffer [ab]", "[Ab]", new StringBuffer("[ab]"), true), + arguments("[abc] does not match a", "[abc]", "a", false), + arguments("[!ab] matches [!ab]", "[!ab]", "[!ab]", true), + arguments("[!ab] does not match c", "[!ab]", "c", false), + arguments("^ matches ^", "^", "^", true), + arguments("() matches ()", "()", "()", true), + arguments("(*) matches (-)", "(*)", "(-)", true), + arguments("$ matches $", "$", "$", true)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("aPatternMatcherTestArguments") + void aPatternMatcherTest(String scenario, String pattern, Object value, boolean matches) { + Matcher matcher = Matchers.compileGlob(pattern); + + assertEquals(matches, matcher.matches(value)); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/util/SimpleRateLimiterTest.java b/dd-trace-core/src/test/java/datadog/trace/core/util/SimpleRateLimiterTest.java new file mode 100644 index 00000000000..4ab8c9a2c4b --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/util/SimpleRateLimiterTest.java @@ -0,0 +1,61 @@ +package datadog.trace.core.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.api.time.ControllableTimeSource; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SimpleRateLimiterTest { + + @ParameterizedTest + @ValueSource(ints = {10, 100, 1000}) + void initialRateAvailableAtCreation(int rate) { + ControllableTimeSource timeSource = new ControllableTimeSource(); + SimpleRateLimiter limiter = new SimpleRateLimiter(rate, timeSource); + + for (int i = 0; i < rate; i++) { + assertTrue(limiter.tryAcquire(), "failed for " + i); + } + + assertFalse(limiter.tryAcquire()); + } + + @ParameterizedTest + @ValueSource(ints = {10, 100, 1000}) + void tokensNeverGoBeyondRate(int rate) { + ControllableTimeSource timeSource = new ControllableTimeSource(); + SimpleRateLimiter limiter = new SimpleRateLimiter(rate, timeSource); + + timeSource.advance(TimeUnit.SECONDS.toNanos(5)); + for (int i = 0; i < rate; i++) { + assertTrue(limiter.tryAcquire(), "failed for " + i); + } + + assertFalse(limiter.tryAcquire()); + } + + @ParameterizedTest + @ValueSource(ints = {10, 100, 1000}) + void tokensAreConsumedAndReplenished(int rate) { + ControllableTimeSource timeSource = new ControllableTimeSource(); + SimpleRateLimiter limiter = new SimpleRateLimiter(rate, timeSource); + long nanosIncrement = TimeUnit.SECONDS.toNanos(1) / (rate + 1) + 1; + + for (int i = 0; i < rate; i++) { + timeSource.advance(nanosIncrement); + assertTrue(limiter.tryAcquire(), "failed for " + i); + } + + assertFalse(limiter.tryAcquire()); + + for (int i = 0; i < rate; i++) { + timeSource.advance(nanosIncrement); + assertTrue(limiter.tryAcquire(), "failed for " + i); + } + + assertFalse(limiter.tryAcquire()); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/util/SystemAccessTest.java b/dd-trace-core/src/test/java/datadog/trace/core/util/SystemAccessTest.java new file mode 100644 index 00000000000..addc90395c6 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/util/SystemAccessTest.java @@ -0,0 +1,65 @@ +package datadog.trace.core.util; + +import static datadog.trace.api.config.GeneralConfig.HEALTH_METRICS_ENABLED; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_ENABLED; +import static datadog.trace.junit.utils.config.WithConfigExtension.injectSysConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.test.util.DDJavaSpecification; +import org.junit.jupiter.api.AfterEach; +import org.tabletest.junit.TableTest; + +class SystemAccessTest extends DDJavaSpecification { + + @AfterEach + void cleanupJmx() { + SystemAccess.disableJmx(); + } + + @TableTest({ + "scenario | providerEnabled | profilingEnabled | healthMetricsEnabled | hasCpuTime", + "all disabled | false | false | false | false ", + "health metrics only | false | false | true | false ", + "profiling only | false | true | false | false ", + "profiling and health metrics | false | true | true | false ", + "provider only | true | false | false | false ", + "provider and health metrics | true | false | true | true ", + "provider and profiling | true | true | false | true ", + "provider, profiling and health metrics | true | true | true | true " + }) + void testCpuTime( + boolean providerEnabled, + boolean profilingEnabled, + boolean healthMetricsEnabled, + boolean hasCpuTime) { + injectSysConfig(PROFILING_ENABLED, String.valueOf(profilingEnabled)); + injectSysConfig(HEALTH_METRICS_ENABLED, String.valueOf(healthMetricsEnabled)); + + if (providerEnabled) { + SystemAccess.enableJmx(); + } else { + SystemAccess.disableJmx(); + } + + long threadCpuTime1 = SystemAccess.getCurrentThreadCpuTime(); + // burn some cpu + int sum = 0; + for (int i = 0; i < 10_000; i++) { + sum += i; + } + long threadCpuTime2 = SystemAccess.getCurrentThreadCpuTime(); + + assertTrue(sum > 0); + + if (hasCpuTime) { + assertNotEquals(Long.MIN_VALUE, threadCpuTime1); + assertNotEquals(Long.MIN_VALUE, threadCpuTime2); + assertTrue(threadCpuTime2 > threadCpuTime1); + } else { + assertEquals(Long.MIN_VALUE, threadCpuTime1); + assertEquals(Long.MIN_VALUE, threadCpuTime2); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollector.java b/internal-api/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollector.java index f2fcd105dc3..b7c0a52a63f 100644 --- a/internal-api/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollector.java +++ b/internal-api/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollector.java @@ -72,4 +72,9 @@ public List getServices() { public void clear() { services.clear(); } + + // Visible for testing + static void setInstance(ServiceNameCollector instance) { + INSTANCE = instance; + } } diff --git a/utils/junit-utils/build.gradle.kts b/utils/junit-utils/build.gradle.kts index f30b2ebca87..f3be8da6480 100644 --- a/utils/junit-utils/build.gradle.kts +++ b/utils/junit-utils/build.gradle.kts @@ -10,6 +10,9 @@ dependencies { api(libs.forbiddenapis) api(project(":components:environment")) + implementation(project(":dd-trace-api")) + implementation(project(":internal-api")) + compileOnly(libs.junit.jupiter) compileOnly(libs.tabletest) } diff --git a/utils/junit-utils/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollectorTestBridge.java b/utils/junit-utils/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollectorTestBridge.java new file mode 100644 index 00000000000..8d66cdb4f83 --- /dev/null +++ b/utils/junit-utils/src/main/java/datadog/trace/api/remoteconfig/ServiceNameCollectorTestBridge.java @@ -0,0 +1,11 @@ +package datadog.trace.api.remoteconfig; + +/** + * Bridge class to allow tests to access package-private method exposed by the {@code + * ServiceNameCollector} + */ +public class ServiceNameCollectorTestBridge { + public static void setInstance(ServiceNameCollector instance) { + ServiceNameCollector.setInstance(instance); + } +} diff --git a/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/ConfigDefaultsConverter.java b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/ConfigDefaultsConverter.java new file mode 100644 index 00000000000..be412469330 --- /dev/null +++ b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/ConfigDefaultsConverter.java @@ -0,0 +1,40 @@ +package datadog.trace.junit.utils.tabletest; + +import datadog.trace.api.ConfigDefaults; +import datadog.trace.api.config.GeneralConfig; +import datadog.trace.api.env.CapturedEnvironment; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; + +public class ConfigDefaultsConverter implements ArgumentConverter { + @Override + public Object convert(Object source, ParameterContext context) + throws ArgumentConversionException { + if (source instanceof Map) { + // convert keys and values from the map + Map map = new HashMap<>(); + for (Map.Entry e : + ((Map) source).entrySet()) { + map.put(convert(e.getKey(), context), convert(e.getValue(), context)); + } + return map; + } + if (source.toString().startsWith("DEFAULT_")) { + switch (source.toString()) { + case "DEFAULT_SERVICE_NAME": + return ConfigDefaults.DEFAULT_SERVICE_NAME; + case "DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME": + return ConfigDefaults.DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME; + default: + throw new ArgumentConversionException("Cannot convert " + source); + } + } + if ("ENV_SERVICE_NAME".equals(source.toString())) { + return CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME); + } + return source.toString(); + } +} diff --git a/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/DDTagsConverter.java b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/DDTagsConverter.java new file mode 100644 index 00000000000..ca8121b6b21 --- /dev/null +++ b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/tabletest/DDTagsConverter.java @@ -0,0 +1,70 @@ +package datadog.trace.junit.utils.tabletest; + +import datadog.trace.api.DDTags; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; + +public class DDTagsConverter implements ArgumentConverter { + @Override + public Object convert(Object source, ParameterContext context) + throws ArgumentConversionException { + if (source instanceof Map) { + // convert keys and values from the map + Map map = new HashMap<>(); + for (Map.Entry e : + ((Map) source).entrySet()) { + map.put(convert(e.getKey(), context), convert(e.getValue(), context)); + } + return map; + } + if (source.toString().startsWith("DDTags.")) { + switch (source.toString()) { + case "DDTags.SPAN_TYPE": + return DDTags.SPAN_TYPE; + case "DDTags.SERVICE_NAME": + return DDTags.SERVICE_NAME; + case "DDTags.RESOURCE_NAME": + return DDTags.RESOURCE_NAME; + case "DDTags.THREAD_NAME": + return DDTags.THREAD_NAME; + case "DDTags.THREAD_ID": + return DDTags.THREAD_ID; + case "DDTags.MANUAL_KEEP": + return DDTags.MANUAL_KEEP; + case "DDTags.MANUAL_DROP": + return DDTags.MANUAL_DROP; + default: + throw new ArgumentConversionException("Cannot convert " + source); + } + } + if (source.toString().startsWith("Tags.")) { + switch (source.toString()) { + case "Tags.SPAN_KIND_SERVER": + return Tags.SPAN_KIND_SERVER; + case "Tags.SPAN_KIND_CLIENT": + return Tags.SPAN_KIND_CLIENT; + case "Tags.SPAN_KIND_PRODUCER": + return Tags.SPAN_KIND_PRODUCER; + case "Tags.SPAN_KIND_CONSUMER": + return Tags.SPAN_KIND_CONSUMER; + case "Tags.SPAN_KIND_BROKER": + return Tags.SPAN_KIND_BROKER; + case "Tags.PEER_SERVICE": + return Tags.PEER_SERVICE; + case "Tags.HTTP_URL": + return Tags.HTTP_URL; + case "Tags.HTTP_STATUS": + return Tags.HTTP_STATUS; + case "Tags.HTTP_METHOD": + return Tags.HTTP_METHOD; + default: + throw new ArgumentConversionException("Cannot convert " + source); + } + } + return source; + } +}