diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java index 5bc985491de..912fcf84a1f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java @@ -80,6 +80,9 @@ final class AggregateEntry extends Hashtable.Entry { DDCaches.newFixedSizeCache(32); private static final DDCache GRPC_STATUS_CODE_CACHE = DDCaches.newFixedSizeCache(32); + // Origin is a small fixed vocabulary (synthetics, synthetics-browser, rum, ciapp-test, lambda). + private static final DDCache ORIGIN_CACHE = + DDCaches.newFixedSizeCache(8); /** * Outer cache keyed by peer-tag name, with an inner per-name cache keyed by value. The inner @@ -108,8 +111,13 @@ final class AggregateEntry extends Hashtable.Entry { @Nullable private final UTF8BytesString grpcStatusCode; private final short httpStatusCode; - /** Whether the root span carried the {@code synthetics} origin tag (synthetic-monitoring run). */ - private final boolean synthetic; + /** + * Trace origin (e.g. {@code synthetics}, {@code rum}, {@code ciapp-test}, {@code lambda}), or + * {@code null} when the root span carried no origin. Part of the bucket key, so spans with + * distinct origins aggregate separately. The OTLP export emits this as {@code datadog.origin}; + * the native msgpack path reads {@link #isSynthetics()}, derived from it. + */ + @Nullable private final UTF8BytesString origin; /** Whether this span is the trace root ({@code parentId == 0}). */ private final boolean traceRoot; @@ -139,7 +147,8 @@ final class AggregateEntry extends Hashtable.Entry { private int errorCount; private int hitCount; private int topLevelCount; - private long duration; + private long okDuration; + private long errorDuration; /** Hot-path constructor for the producer/consumer flow. Builds UTF8 fields via the caches. */ AggregateEntry(SpanSnapshot s, long keyHash) { @@ -154,7 +163,7 @@ final class AggregateEntry extends Hashtable.Entry { this.httpEndpoint = canonicalizeOptional(HTTP_ENDPOINT_CACHE, s.httpEndpoint); this.grpcStatusCode = canonicalizeOptional(GRPC_STATUS_CODE_CACHE, s.grpcStatusCode); this.httpStatusCode = s.httpStatusCode; - this.synthetic = s.synthetic; + this.origin = canonicalizeOptional(ORIGIN_CACHE, s.origin); this.traceRoot = s.traceRoot; this.peerTagNames = s.peerTagSchema == null ? null : s.peerTagSchema.names; this.peerTagValues = s.peerTagValues; @@ -174,11 +183,12 @@ void recordOneDuration(long tagAndDuration) { if ((tagAndDuration & ERROR_TAG) == ERROR_TAG) { tagAndDuration ^= ERROR_TAG; errorLatenciesForWrite().accept(tagAndDuration); + errorDuration += tagAndDuration; ++errorCount; } else { okLatencies.accept(tagAndDuration); + okDuration += tagAndDuration; } - duration += tagAndDuration; } int getErrorCount() { @@ -194,7 +204,15 @@ int getTopLevelCount() { } long getDuration() { - return duration; + return okDuration + errorDuration; + } + + long getOkDuration() { + return okDuration; + } + + long getErrorDuration() { + return errorDuration; } Histogram getOkLatencies() { @@ -232,7 +250,8 @@ void clear() { this.errorCount = 0; this.hitCount = 0; this.topLevelCount = 0; - this.duration = 0; + this.okDuration = 0; + this.errorDuration = 0; this.okLatencies.clear(); // errorLatencies stays null on entries that never errored. Only clear if it was allocated. if (this.errorLatencies != null) { @@ -243,7 +262,7 @@ void clear() { boolean matches(SpanSnapshot s) { String[] snapshotNames = s.peerTagSchema == null ? null : s.peerTagSchema.names; return httpStatusCode == s.httpStatusCode - && synthetic == s.synthetic + && contentEquals(origin, s.origin) && traceRoot == s.traceRoot && contentEquals(resource, s.resourceName) && contentEquals(service, s.serviceName) @@ -284,7 +303,7 @@ static long hashOf(SpanSnapshot s) { h = LongHashingUtils.addToHash(h, s.serviceNameSource); h = LongHashingUtils.addToHash(h, s.spanType); h = LongHashingUtils.addToHash(h, s.httpStatusCode); - h = LongHashingUtils.addToHash(h, s.synthetic); + h = LongHashingUtils.addToHash(h, s.origin); h = LongHashingUtils.addToHash(h, s.traceRoot); h = LongHashingUtils.addToHash(h, s.spanKind); // Always mix in both the schema's content hash and the values' content hash, unconditionally @@ -352,8 +371,21 @@ int getHttpStatusCode() { return httpStatusCode; } + /** + * The full trace origin, or {@code null} when unset. Used by {@link OtlpStatsMetricWriter} to + * emit {@code datadog.origin}. + */ + @Nullable + UTF8BytesString getOrigin() { + return origin; + } + + /** + * Whether the origin is {@code synthetics}. Derived from {@link #origin} for the native msgpack + * writer, which emits a synthetics boolean rather than the full origin. + */ boolean isSynthetics() { - return synthetic; + return origin != null && "synthetics".contentEquals(origin); } boolean isTraceRoot() { 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 895ee434854..594d3e6c4c0 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 @@ -47,8 +47,6 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve private static final Map DEFAULT_HEADERS = Collections.singletonMap(DDAgentApi.DATADOG_META_TRACER_VERSION, DDTraceCoreInfo.VERSION); - private static final CharSequence SYNTHETICS_ORIGIN = "synthetics"; - private static final SpanKindFilter METRICS_ELIGIBLE_KINDS = SpanKindFilter.builder() .includeServer() @@ -346,7 +344,7 @@ private boolean publish(CoreSpan span, boolean isTopLevel) { span.getServiceNameSource(), spanType, span.getHttpStatusCode(), - isSynthetic(span), + span.getOrigin(), span.getParentId() == 0, spanKind, peerTagSchema, @@ -466,10 +464,6 @@ private static String[] capturePeerTagValues(CoreSpan span, PeerTagSchema sch return values; } - private static boolean isSynthetic(CoreSpan span) { - return span.getOrigin() != null && SYNTHETICS_ORIGIN.equals(span.getOrigin().toString()); - } - public void stop() { if (null != cancellation) { cancellation.cancel(); diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/SpanSnapshot.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/SpanSnapshot.java index 152ac42bb55..a462e5968e9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/SpanSnapshot.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/SpanSnapshot.java @@ -16,7 +16,16 @@ final class SpanSnapshot implements InboxItem { final CharSequence serviceNameSource; final CharSequence spanType; final short httpStatusCode; - final boolean synthetic; + + /** + * Trace origin (e.g. {@code synthetics}, {@code synthetics-browser}, {@code rum}, {@code + * ciapp-test}, {@code lambda}), or {@code null} when the root span carried no origin. Captured in + * full -- rather than collapsed to a synthetics flag -- so the OTLP export can emit {@code + * datadog.origin} with the recognized value; the native msgpack path derives its synthetics + * boolean from it via {@link AggregateEntry#isSynthetics()}. + */ + final CharSequence origin; + final boolean traceRoot; final String spanKind; @@ -48,7 +57,7 @@ final class SpanSnapshot implements InboxItem { CharSequence serviceNameSource, CharSequence spanType, short httpStatusCode, - boolean synthetic, + CharSequence origin, boolean traceRoot, String spanKind, PeerTagSchema peerTagSchema, @@ -63,7 +72,7 @@ final class SpanSnapshot implements InboxItem { this.serviceNameSource = serviceNameSource; this.spanType = spanType; this.httpStatusCode = httpStatusCode; - this.synthetic = synthetic; + this.origin = origin; this.traceRoot = traceRoot; this.spanKind = spanKind; this.peerTagSchema = peerTagSchema; diff --git a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTest.java b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTest.java index 7fd767533c7..02693ee0edd 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTest.java @@ -131,7 +131,7 @@ private static SpanSnapshot snapshotWithPeerTags(String[] names, String[] values null, "type", (short) 200, - false, + null, true, "client", PeerTagSchema.testSchema(names), @@ -151,7 +151,7 @@ private static AggregateEntry newEntry() { null, "type", (short) 200, - false, + null, true, "client", null, diff --git a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTestUtils.java b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTestUtils.java index ed6fd5a3a7e..a36ca8b907f 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTestUtils.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateEntryTestUtils.java @@ -76,7 +76,9 @@ public static AggregateEntry of( serviceSource, type, (short) httpStatusCode, - synthetic, + // The legacy boolean maps onto the full origin field: true => "synthetics", false => + // no origin. Tests needing a non-synthetics origin use ofOrigin(...). + synthetic ? "synthetics" : null, traceRoot, spanKind == null ? null : spanKind.toString(), schema, @@ -88,6 +90,38 @@ public static AggregateEntry of( return forSnapshot(syntheticSnapshot); } + /** + * Builds a minimal {@link AggregateEntry} carrying an explicit trace {@code origin} (e.g. {@code + * rum}, {@code ciapp-test}, {@code lambda}). A trace-root server entry with no HTTP/RPC/peer-tag + * fields; durations are recorded by the caller. + */ + public static AggregateEntry ofOrigin( + CharSequence resource, + CharSequence service, + CharSequence operationName, + CharSequence type, + CharSequence spanKind, + @Nullable CharSequence origin) { + SpanSnapshot snapshot = + new SpanSnapshot( + resource, + service == null ? null : service.toString(), + operationName, + null, + type, + (short) 0, + origin, + true, + spanKind == null ? null : spanKind.toString(), + null, + null, + null, + null, + null, + 0L); + return forSnapshot(snapshot); + } + /** * Builds an {@link AggregateEntry} from {@code s} by computing its lookup hash via {@link * AggregateEntry#hashOf(SpanSnapshot)} and calling the package-private constructor directly. @@ -106,7 +140,7 @@ public static boolean equals(AggregateEntry a, AggregateEntry b) { if (a == b) return true; if (a == null || b == null) return false; return a.getHttpStatusCode() == b.getHttpStatusCode() - && a.isSynthetics() == b.isSynthetics() + && Objects.equals(a.getOrigin(), b.getOrigin()) && a.isTraceRoot() == b.isTraceRoot() && Objects.equals(a.getResource(), b.getResource()) && Objects.equals(a.getService(), b.getService()) diff --git a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateTableTest.java b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateTableTest.java index 618ead2ab43..c3463337079 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateTableTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/metrics/AggregateTableTest.java @@ -274,7 +274,7 @@ private static SpanSnapshot nullServiceKindSnapshot(String service, String spanK null, "web", (short) 200, - false, + null, true, spanKind, null, @@ -294,7 +294,7 @@ private static SpanSnapshot nullableSnapshot( serviceNameSource, type, (short) 200, - false, + null, true, "client", null, @@ -350,7 +350,7 @@ SpanSnapshot build() { null, "web", (short) 200, - false, + null, true, spanKind, peerTagSchema,