diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java index f6e35e3a755..d9986548b25 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java @@ -331,6 +331,7 @@ private boolean publish(CoreSpan span, boolean isTopLevel, CharSequence spanK span.getResourceName(), SERVICE_NAMES.computeIfAbsent(span.getServiceName(), UTF8_ENCODE), span.getOperationName(), + span.getServiceNameSource(), span.getType(), span.getHttpStatusCode(), isSynthetic(span), diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricKey.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricKey.java index 105d1ff01a6..e5d2a4d5bb5 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricKey.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricKey.java @@ -5,11 +5,13 @@ import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import java.util.Collections; import java.util.List; +import java.util.Objects; /** The aggregation key for tracked metrics. */ public final class MetricKey { private final UTF8BytesString resource; private final UTF8BytesString service; + private final UTF8BytesString serviceSource; private final UTF8BytesString operationName; private final UTF8BytesString type; private final int httpStatusCode; @@ -21,35 +23,11 @@ public final class MetricKey { private final UTF8BytesString httpMethod; private final UTF8BytesString httpEndpoint; - // Constructor without httpMethod and httpEndpoint for backward compatibility - public MetricKey( - CharSequence resource, - CharSequence service, - CharSequence operationName, - CharSequence type, - int httpStatusCode, - boolean synthetics, - boolean isTraceRoot, - CharSequence spanKind, - List peerTags) { - this( - resource, - service, - operationName, - type, - httpStatusCode, - synthetics, - isTraceRoot, - spanKind, - peerTags, - null, - null); - } - public MetricKey( CharSequence resource, CharSequence service, CharSequence operationName, + CharSequence serviceSource, CharSequence type, int httpStatusCode, boolean synthetics, @@ -60,6 +38,7 @@ public MetricKey( CharSequence httpEndpoint) { this.resource = null == resource ? EMPTY : UTF8BytesString.create(resource); this.service = null == service ? EMPTY : UTF8BytesString.create(service); + this.serviceSource = null == serviceSource ? null : UTF8BytesString.create(serviceSource); this.operationName = null == operationName ? EMPTY : UTF8BytesString.create(operationName); this.type = null == type ? EMPTY : UTF8BytesString.create(type); this.httpStatusCode = httpStatusCode; @@ -79,18 +58,34 @@ public MetricKey( // Only include httpMethod and httpEndpoint in hash if they are not null // This ensures backward compatibility when the feature is disabled - this.hash = - -196_513_505 * Boolean.hashCode(this.isTraceRoot) - + -1_807_454_463 * this.spanKind.hashCode() - + 887_503_681 * this.peerTags.hashCode() - + (this.httpMethod != null ? 28_629_151 * this.httpMethod.hashCode() : 0) - + (this.httpEndpoint != null ? 923_521 * this.httpEndpoint.hashCode() : 0) - + 29_791 * this.resource.hashCode() - + 961 * this.service.hashCode() - + 31 * this.operationName.hashCode() - + this.type.hashCode() - + 31 * httpStatusCode - + (this.synthetics ? 1 : 0); + + // Note: all the multiplication got constant folded at compile time (see javap -verbose ...) + int tmpHash = + (int) (31L * 31 * 31 * 31 * 31 * 31 * 31 * 31) * Boolean.hashCode(this.isTraceRoot) // 8 + + (int) (31L * 31 * 31 * 31 * 31 * 31 * 31) * this.spanKind.hashCode() // 7 + + 31 * 31 * 31 * 31 * 31 * 31 * this.peerTags.hashCode() // 6 + + 31 * 31 * 31 * 31 * 31 * this.resource.hashCode() // 5 + + 31 * 31 * 31 * 31 * this.service.hashCode() // 4 + + 31 * 31 * 31 * this.operationName.hashCode() // 3 + + 31 * 31 * this.type.hashCode() // 2 + + 31 * this.httpStatusCode // 1 + + (this.synthetics ? 1 : 0); // 0 + // optional fields + if (this.serviceSource != null) { + tmpHash += + (int) (31L * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31) * this.serviceSource.hashCode(); // 9 + } + if (this.httpEndpoint != null) { + tmpHash += + (int) (31L * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31) + * this.httpEndpoint.hashCode(); // 10 + } + if (this.httpMethod != null) { + tmpHash += + (int) (31L * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31 * 31) + * this.httpMethod.hashCode(); // 11 + } + this.hash = tmpHash; } public UTF8BytesString getResource() { @@ -101,6 +96,10 @@ public UTF8BytesString getService() { return service; } + public UTF8BytesString getServiceSource() { + return serviceSource; + } + public UTF8BytesString getOperationName() { return operationName; } @@ -144,29 +143,19 @@ public boolean equals(Object o) { } if ((o instanceof MetricKey)) { MetricKey metricKey = (MetricKey) o; - boolean basicEquals = - hash == metricKey.hash - && synthetics == metricKey.synthetics - && httpStatusCode == metricKey.httpStatusCode - && resource.equals(metricKey.resource) - && service.equals(metricKey.service) - && operationName.equals(metricKey.operationName) - && type.equals(metricKey.type) - && isTraceRoot == metricKey.isTraceRoot - && spanKind.equals(metricKey.spanKind) - && peerTags.equals(metricKey.peerTags); - - // Only compare httpMethod and httpEndpoint if at least one of them is not null - // This ensures backward compatibility when the feature is disabled - boolean thisHasEndpoint = httpMethod != null || httpEndpoint != null; - boolean otherHasEndpoint = metricKey.httpMethod != null || metricKey.httpEndpoint != null; - - if (thisHasEndpoint || otherHasEndpoint) { - return basicEquals - && java.util.Objects.equals(httpMethod, metricKey.httpMethod) - && java.util.Objects.equals(httpEndpoint, metricKey.httpEndpoint); - } - return basicEquals; + return hash == metricKey.hash + && synthetics == metricKey.synthetics + && httpStatusCode == metricKey.httpStatusCode + && resource.equals(metricKey.resource) + && service.equals(metricKey.service) + && operationName.equals(metricKey.operationName) + && type.equals(metricKey.type) + && isTraceRoot == metricKey.isTraceRoot + && spanKind.equals(metricKey.spanKind) + && peerTags.equals(metricKey.peerTags) + && Objects.equals(serviceSource, metricKey.serviceSource) + && Objects.equals(httpMethod, metricKey.httpMethod) + && Objects.equals(httpEndpoint, metricKey.httpEndpoint); } return false; } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/SerializingMetricWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/SerializingMetricWriter.java index de16ec41fda..9d9bd34b5df 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/SerializingMetricWriter.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/SerializingMetricWriter.java @@ -37,6 +37,7 @@ public final class SerializingMetricWriter implements MetricWriter { private static final byte[] PEER_TAGS = "PeerTags".getBytes(ISO_8859_1); private static final byte[] HTTP_METHOD = "HTTPMethod".getBytes(ISO_8859_1); private static final byte[] HTTP_ENDPOINT = "HTTPEndpoint".getBytes(ISO_8859_1); + private static final byte[] SERVICE_SOURCE = "srv_src".getBytes(ISO_8859_1); // Constant declared here for compile-time folding public static final int TRISTATE_TRUE = TriState.TRUE.serialValue; @@ -109,7 +110,9 @@ public void add(MetricKey key, AggregateMetric aggregate) { // Calculate dynamic map size based on optional fields final boolean hasHttpMethod = key.getHttpMethod() != null; final boolean hasHttpEndpoint = key.getHttpEndpoint() != null; - final int mapSize = 15 + (hasHttpMethod ? 1 : 0) + (hasHttpEndpoint ? 1 : 0); + final boolean hasServiceSource = key.getServiceSource() != null; + final int mapSize = + 15 + (hasServiceSource ? 1 : 0) + (hasHttpMethod ? 1 : 0) + (hasHttpEndpoint ? 1 : 0); writer.startMap(mapSize); @@ -145,6 +148,10 @@ public void add(MetricKey key, AggregateMetric aggregate) { writer.writeUTF8(peerTag); } + if (hasServiceSource) { + writer.writeUTF8(SERVICE_SOURCE); + writer.writeUTF8(key.getServiceSource()); + } // Only include HTTPMethod if present if (hasHttpMethod) { writer.writeUTF8(HTTP_METHOD); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreSpan.java index 4de7495b929..9d2164a7606 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreSpan.java @@ -9,6 +9,10 @@ public interface CoreSpan> { String getServiceName(); + default CharSequence getServiceNameSource() { + return null; + } + CharSequence getOperationName(); CharSequence getResourceName(); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index 745f1249fc5..63dd1010301 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -590,6 +590,11 @@ public void setServiceName(@Nonnull String serviceName, @Nonnull CharSequence so context.setServiceName(serviceName, source); } + @Override + public CharSequence getServiceNameSource() { + return context.getServiceNameSource(); + } + @Override public final DDSpan setResourceName(final CharSequence resourceName) { return setResourceName(resourceName, ResourceNamePriorities.DEFAULT); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java index a69c057646e..30226030b44 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java @@ -13,7 +13,7 @@ private static class Lazy { private static TagsPostProcessor lazyProcessor = createLazyChain(); private static TagsPostProcessor createEagerChain() { - final List processors = new ArrayList<>(4); + final List processors = new ArrayList<>(3); processors.add(new PeerServiceCalculator()); if (addBaseService) { processors.add(new BaseServiceAdder(Config.get().getServiceName())); @@ -23,12 +23,11 @@ private static TagsPostProcessor createEagerChain() { if (Config.get().isTraceResourceRenamingEnabled()) { processors.add(new HttpEndpointPostProcessor()); } - processors.add(new ServiceNameSourceAdder()); // eager since needed for stats return new PostProcessorChain(processors.toArray(new TagsPostProcessor[0])); } private static TagsPostProcessor createLazyChain() { - final List processors = new ArrayList<>(7); + final List processors = new ArrayList<>(8); processors.add(new QueryObfuscator(Config.get().getObfuscationQueryRegexp())); if (addRemoteHostname) { @@ -48,6 +47,7 @@ private static TagsPostProcessor createLazyChain() { processors.add(new SpanPointersProcessor()); } processors.add(new IntegrationAdder()); + processors.add(new ServiceNameSourceAdder()); return new PostProcessorChain( processors.toArray(processors.toArray(new TagsPostProcessor[0]))); } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/AggregateMetricTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/AggregateMetricTest.groovy index db578fc96c1..8990ff22712 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/AggregateMetricTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/AggregateMetricTest.groovy @@ -65,7 +65,7 @@ class AggregateMetricTest extends DDSpecification { given: AggregateMetric aggregate = new AggregateMetric().recordDurations(3, new AtomicLongArray(0L, 0L, 0L | ERROR_TAG | TOP_LEVEL_TAG)) - Batch batch = new Batch().reset(new MetricKey("foo", "bar", "qux", "type", 0, false, true, "corge", [UTF8BytesString.create("grault:quux")], null, null)) + Batch batch = new Batch().reset(new MetricKey("foo", "bar", "qux", null, "type", 0, false, true, "corge", [UTF8BytesString.create("grault:quux")], null, null)) batch.add(0L, 10) batch.add(0L, 10) batch.add(0L, 10) @@ -140,7 +140,7 @@ class AggregateMetricTest extends DDSpecification { def "consistent under concurrent attempts to read and write"() { given: AggregateMetric aggregate = new AggregateMetric() - MetricKey key = new MetricKey("foo", "bar", "qux", "type", 0, false, true, "corge", [UTF8BytesString.create("grault:quux")], null, null) + MetricKey key = new MetricKey("foo", "bar", "qux", null, "type", 0, false, true, "corge", [UTF8BytesString.create("grault:quux")], null, null) BlockingDeque queue = new LinkedBlockingDeque<>(1000) ExecutorService reader = Executors.newSingleThreadExecutor() int writerCount = 10 diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/ConflatingMetricAggregatorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/ConflatingMetricAggregatorTest.groovy index b338068462c..1cc3389235d 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/ConflatingMetricAggregatorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/ConflatingMetricAggregatorTest.groovy @@ -122,6 +122,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { null, "service", "operation", + null, "type", HTTP_OK, false, @@ -166,6 +167,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -216,6 +218,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -276,6 +279,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -292,6 +296,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -337,6 +342,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -387,6 +393,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -444,6 +451,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -459,6 +467,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource2", "service2", "operation2", + null, "type", HTTP_OK, false, @@ -511,6 +520,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -550,6 +560,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -565,6 +576,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -580,6 +592,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -642,6 +655,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 200, false, @@ -657,6 +671,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 200, false, @@ -672,6 +687,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 404, false, @@ -687,6 +703,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 200, false, @@ -738,6 +755,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 200, false, @@ -753,6 +771,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", 200, false, @@ -770,6 +789,72 @@ class ConflatingMetricAggregatorTest extends DDSpecification { aggregator.close() } + def "gather the service name source when the span is published"() { + setup: + MetricWriter writer = Mock(MetricWriter) + Sink sink = Stub(Sink) + DDAgentFeaturesDiscovery features = Mock(DDAgentFeaturesDiscovery) + features.supportsMetrics() >> true + features.peerTags() >> [] + ConflatingMetricsAggregator aggregator = new ConflatingMetricsAggregator(empty, + features, HealthMetrics.NO_OP, sink, writer, 10, queueSize, reportingInterval, SECONDS, false) + aggregator.start() + + when: "publish spans with different service name source" + CountDownLatch latch = new CountDownLatch(1) + long duration = 100 + aggregator.publish([ + new SimpleSpan("service", "operation", "resource", "type", true, true, false, 0, duration, 200, false, 0, "source") + .setTag(SPAN_KIND, "server"), + new SimpleSpan("service", "operation", "resource", "type", true, true, false, 0, duration, 200, false, 0, null) + .setTag(SPAN_KIND, "server"), + new SimpleSpan("service", "operation", "resource", "type", true, true, false, 0, duration, 200, false, 0, "source") + .setTag(SPAN_KIND, "server") + ]) + aggregator.report() + def latchTriggered = latch.await(2, SECONDS) + + then: "should create the different metric keys for spans with and without sources" + latchTriggered + 1 * writer.startBucket(2, _, _) + 1 * writer.add(new MetricKey( + "resource", + "service", + "operation", + "source", + "type", + 200, + false, + false, + "server", + [], + null, + null + ), { AggregateMetric value -> + value.getHitCount() == 2 && value.getDuration() == 2 * duration + }) + 1 * writer.add(new MetricKey( + "resource", + "service", + "operation", + null, + "type", + 200, + false, + false, + "server", + [], + null, + null + ), { AggregateMetric value -> + value.getHitCount() == 1 && value.getDuration() == duration + }) + 1 * writer.finishBucket() >> { latch.countDown() } + + cleanup: + aggregator.close() + } + def "test least recently written to aggregate flushed when size limit exceeded"() { setup: int maxAggregates = 10 @@ -802,6 +887,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service" + i, "operation", + null, "type", HTTP_OK, false, @@ -818,6 +904,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service0", "operation", + null, "type", HTTP_OK, false, @@ -865,6 +952,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service" + i, "operation", + null, "type", HTTP_OK, false, @@ -898,6 +986,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service" + i, "operation", + null, "type", HTTP_OK, false, @@ -914,6 +1003,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service0", "operation", + null, "type", HTTP_OK, false, @@ -961,6 +1051,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service" + i, "operation", + null, "type", HTTP_OK, false, @@ -1018,6 +1109,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service" + i, "operation", + null, "type", HTTP_OK, false, @@ -1183,6 +1275,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -1236,6 +1329,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -1289,6 +1383,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -1305,6 +1400,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, @@ -1321,6 +1417,7 @@ class ConflatingMetricAggregatorTest extends DDSpecification { "resource", "service", "operation", + null, "type", HTTP_OK, false, diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SerializingMetricWriterTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SerializingMetricWriterTest.groovy index 088a76e7957..92bbfe53341 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SerializingMetricWriterTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SerializingMetricWriterTest.groovy @@ -1,22 +1,21 @@ package datadog.trace.common.metrics +import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED +import static java.util.concurrent.TimeUnit.MILLISECONDS +import static java.util.concurrent.TimeUnit.SECONDS + import datadog.metrics.api.Histograms import datadog.metrics.impl.DDSketchHistograms import datadog.trace.api.Config +import datadog.trace.api.Pair import datadog.trace.api.ProcessTags import datadog.trace.api.WellKnownTags -import datadog.trace.api.Pair import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString import datadog.trace.test.util.DDSpecification -import org.msgpack.core.MessagePack -import org.msgpack.core.MessageUnpacker - import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicLongArray - -import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED -import static java.util.concurrent.TimeUnit.MILLISECONDS -import static java.util.concurrent.TimeUnit.SECONDS +import org.msgpack.core.MessagePack +import org.msgpack.core.MessageUnpacker class SerializingMetricWriterTest extends DDSpecification { @@ -55,6 +54,7 @@ class SerializingMetricWriterTest extends DDSpecification { "resource1", "service1", "operation1", + null, "type", 0, false, @@ -75,6 +75,7 @@ class SerializingMetricWriterTest extends DDSpecification { "resource2", "service2", "operation2", + null, "type2", 200, true, @@ -95,6 +96,7 @@ class SerializingMetricWriterTest extends DDSpecification { "GET /api/users/:id", "web-service", "http.request", + null, "web", 200, false, @@ -113,6 +115,7 @@ class SerializingMetricWriterTest extends DDSpecification { "resource" + i, "service" + i, "operation" + i, + null, "type", 0, false, @@ -189,7 +192,8 @@ class SerializingMetricWriterTest extends DDSpecification { // Calculate expected map size based on optional fields boolean hasHttpMethod = key.getHttpMethod() != null boolean hasHttpEndpoint = key.getHttpEndpoint() != null - int expectedMapSize = 15 + (hasHttpMethod ? 1 : 0) + (hasHttpEndpoint ? 1 : 0) + boolean hasServiceSource = key.getServiceSource() != null + int expectedMapSize = 15 + (hasServiceSource ? 1 : 0) + (hasHttpMethod ? 1 : 0) + (hasHttpEndpoint ? 1 : 0) assert metricMapSize == expectedMapSize int elementCount = 0 assert unpacker.unpackString() == "Name" @@ -224,6 +228,12 @@ class SerializingMetricWriterTest extends DDSpecification { assert unpackedPeerTag == key.getPeerTags()[i].toString() } ++elementCount + // Service source is only present when the service name has been overridden by the tracer + if (hasServiceSource) { + assert unpacker.unpackString() == "srv_src" + assert unpacker.unpackString() == key.getServiceSource().toString() + ++elementCount + } // HTTPMethod and HTTPEndpoint are optional - only present if non-null if (hasHttpMethod) { assert unpacker.unpackString() == "HTTPMethod" @@ -269,6 +279,35 @@ class SerializingMetricWriterTest extends DDSpecification { } } + def "ServiceSource optional in the payload"() { + setup: + long startTime = MILLISECONDS.toNanos(System.currentTimeMillis()) + long duration = SECONDS.toNanos(10) + WellKnownTags wellKnownTags = new WellKnownTags("runtimeid", "hostname", "env", "service", "version", "language") + + // Create keys with different combinations of HTTP fields + def keyWithNoSource = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "GET", "/api/users") + def keyWithSource = new MetricKey("resource", "service", "operation", "source", "type", 200, false, false, "server", [], "POST", null) + + def content = [ + Pair.of(keyWithNoSource, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))), + Pair.of(keyWithSource, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))), + ] + + ValidatingSink sink = new ValidatingSink(wellKnownTags, startTime, duration, content) + SerializingMetricWriter writer = new SerializingMetricWriter(wellKnownTags, sink, 128) + + when: + writer.startBucket(content.size(), startTime, duration) + for (Pair pair : content) { + writer.add(pair.getLeft(), pair.getRight()) + } + writer.finishBucket() + + then: + sink.validatedInput() + } + def "HTTPMethod and HTTPEndpoint fields are optional in payload"() { setup: long startTime = MILLISECONDS.toNanos(System.currentTimeMillis()) @@ -276,10 +315,10 @@ class SerializingMetricWriterTest extends DDSpecification { WellKnownTags wellKnownTags = new WellKnownTags("runtimeid", "hostname", "env", "service", "version", "language") // Create keys with different combinations of HTTP fields - def keyWithBoth = new MetricKey("resource", "service", "operation", "type", 200, false, false, "server", [], "GET", "/api/users") - def keyWithMethodOnly = new MetricKey("resource", "service", "operation", "type", 200, false, false, "server", [], "POST", null) - def keyWithEndpointOnly = new MetricKey("resource", "service", "operation", "type", 200, false, false, "server", [], null, "/api/orders") - def keyWithNeither = new MetricKey("resource", "service", "operation", "type", 200, false, false, "client", [], null, null) + def keyWithBoth = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "GET", "/api/users") + def keyWithMethodOnly = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "POST", null) + def keyWithEndpointOnly = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], null, "/api/orders") + def keyWithNeither = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "client", [], null, null) def content = [ Pair.of(keyWithBoth, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))), diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SimpleSpan.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SimpleSpan.groovy index ec2b3b73ce5..e91ee25f8f0 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SimpleSpan.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SimpleSpan.groovy @@ -10,6 +10,7 @@ class SimpleSpan implements CoreSpan { private final String serviceName private final String operationName private final CharSequence resourceName + private final CharSequence serviceNameSource private final String type private final boolean measured private final boolean topLevel @@ -35,11 +36,12 @@ class SimpleSpan implements CoreSpan { long duration, int statusCode, boolean traceRoot = false, - int longRunningVersion = 0 - ) { + int longRunningVersion = 0, + CharSequence serviceNameSource = null) { this.serviceName = serviceName this.operationName = operationName this.resourceName = resourceName + this.serviceNameSource = serviceNameSource this.type = type this.measured = measured this.topLevel = topLevel @@ -61,6 +63,11 @@ class SimpleSpan implements CoreSpan { return serviceName } + @Override + CharSequence getServiceNameSource() { + return serviceNameSource + } + @Override CharSequence getOperationName() { return operationName diff --git a/dd-trace-core/src/traceAgentTest/groovy/MetricsIntegrationTest.groovy b/dd-trace-core/src/traceAgentTest/groovy/MetricsIntegrationTest.groovy index 3316d79c4de..7323943a3ee 100644 --- a/dd-trace-core/src/traceAgentTest/groovy/MetricsIntegrationTest.groovy +++ b/dd-trace-core/src/traceAgentTest/groovy/MetricsIntegrationTest.groovy @@ -40,11 +40,11 @@ class MetricsIntegrationTest extends AbstractTraceAgentTest { ) writer.startBucket(2, System.nanoTime(), SECONDS.toNanos(10)) writer.add( - new MetricKey("resource1", "service1", "operation1", "sql", 0, false, true, "xyzzy", [UTF8BytesString.create("grault:quux")], null, null), + new MetricKey("resource1", "service1", "operation1", null, "sql", 0, false, true, "xyzzy", [UTF8BytesString.create("grault:quux")], null, null), new AggregateMetric().recordDurations(5, new AtomicLongArray(2, 1, 2, 250, 4, 5)) ) writer.add( - new MetricKey("resource2", "service2", "operation2", "web", 200, false, true, "xyzzy", [UTF8BytesString.create("grault:quux")], null, null), + new MetricKey("resource2", "service2", "operation2", null, "web", 200, false, true, "xyzzy", [UTF8BytesString.create("grault:quux")], null, null), new AggregateMetric().recordDurations(10, new AtomicLongArray(1, 1, 200, 2, 3, 4, 5, 6, 7, 8, 9)) ) writer.finishBucket()