diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java index 7cd15f6077a..feffd6f78f4 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java @@ -14,6 +14,9 @@ final class ImmutableTraceFlags implements TraceFlags { private static final ImmutableTraceFlags[] INSTANCES = buildInstances(); // Bit to represent whether trace is sampled or not. private static final byte SAMPLED_BIT = 0x01; + static final int CONTEXT_HAS_IS_REMOTE_BIT = 0x00000100; + static final int CONTEXT_IS_REMOTE_BIT = 0x00000200; + static final int CONTEXT_IS_REMOTE_MASK = CONTEXT_HAS_IS_REMOTE_BIT | CONTEXT_IS_REMOTE_BIT; static final ImmutableTraceFlags DEFAULT = fromByte((byte) 0x00); static final ImmutableTraceFlags SAMPLED = fromByte(SAMPLED_BIT); @@ -65,6 +68,15 @@ public byte asByte() { return this.byteRep; } + @Override + public int withParentIsRemoteFlags(boolean isParentRemote) { + if (isParentRemote) { + return (this.byteRep & 0xff) | CONTEXT_IS_REMOTE_MASK; + } else { + return (this.byteRep & 0xff) | CONTEXT_HAS_IS_REMOTE_BIT; + } + } + @Override public String toString() { return asHex(); diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java index 936a1a6e06c..c0c9195b141 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java @@ -44,6 +44,28 @@ static TraceFlags getSampled() { return ImmutableTraceFlags.SAMPLED; } + /** + * Returns the int (fixed32) representation of the 4 bytes flags with the + * has_parent_context_is_remote flag bit on. + * + * @return the int (fixed32) representation of the 4 bytes flags with the * + * has_parent_context_is_remote flag bit on. + */ + static int getHasParentIsRemote() { + return ImmutableTraceFlags.CONTEXT_HAS_IS_REMOTE_BIT; + } + + /** + * Returns the int (fixed32) representation of the 4 bytes flags with the + * has_parent_context_is_remote and parent_context_is_remote flag bits on. + * + * @return the int (fixed32) representation of the 4 bytes flags with the + * has_parent_context_is_remote and parent_context_is_remote flag bits on. + */ + static int getParentIsRemote() { + return ImmutableTraceFlags.CONTEXT_IS_REMOTE_MASK; + } + /** * Returns the {@link TraceFlags} converted from the given lowercase hex (base16) representation. * @@ -89,4 +111,14 @@ static TraceFlags fromByte(byte traceFlagsByte) { * @return the byte representation of the {@link TraceFlags}. */ byte asByte(); + + /** + * Returns the int (fixed32) representation of this {@link TraceFlags} enriched with the flags + * indicating a remote parent. + * + * @param isParentRemote indicates whether the parent context is remote + * @return the int (fixed32) representation of this {@link TraceFlags} enriched with the flags + * indicating a remote parent. + */ + int withParentIsRemoteFlags(boolean isParentRemote); } diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java index d6ef529f004..44373d2c03e 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java @@ -43,4 +43,17 @@ void toFromByte() { assertThat(TraceFlags.fromByte((byte) i).asByte()).isEqualTo((byte) i); } } + + @Test + void withParentIsRemoteFlags() { + assertThat(TraceFlags.fromByte((byte) 0xff).withParentIsRemoteFlags(false)).isEqualTo(0x1ff); + assertThat(TraceFlags.fromByte((byte) 0x01).withParentIsRemoteFlags(false)).isEqualTo(0x101); + assertThat(TraceFlags.fromByte((byte) 0x05).withParentIsRemoteFlags(false)).isEqualTo(0x105); + assertThat(TraceFlags.fromByte((byte) 0x00).withParentIsRemoteFlags(false)).isEqualTo(0x100); + + assertThat(TraceFlags.fromByte((byte) 0xff).withParentIsRemoteFlags(true)).isEqualTo(0x3ff); + assertThat(TraceFlags.fromByte((byte) 0x01).withParentIsRemoteFlags(true)).isEqualTo(0x301); + assertThat(TraceFlags.fromByte((byte) 0x05).withParentIsRemoteFlags(true)).isEqualTo(0x305); + assertThat(TraceFlags.fromByte((byte) 0x00).withParentIsRemoteFlags(true)).isEqualTo(0x300); + } } diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanLinkMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanLinkMarshaler.java index 6adcac370e1..89dbe4bc8fe 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanLinkMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanLinkMarshaler.java @@ -28,6 +28,7 @@ final class SpanLinkMarshaler extends MarshalerWithSize { private final KeyValueMarshaler[] attributeMarshalers; private final int droppedAttributesCount; private final TraceFlags traceFlags; + private final boolean isLinkContextRemote; static SpanLinkMarshaler[] createRepeated(List links) { if (links.isEmpty()) { @@ -50,13 +51,15 @@ static SpanLinkMarshaler create(LinkData link) { traceState.isEmpty() ? EMPTY_BYTES : encodeTraceState(traceState).getBytes(StandardCharsets.UTF_8); + return new SpanLinkMarshaler( link.getSpanContext().getTraceId(), link.getSpanContext().getSpanId(), link.getSpanContext().getTraceFlags(), traceStateUtf8, KeyValueMarshaler.createForAttributes(link.getAttributes()), - link.getTotalAttributeCount() - link.getAttributes().size()); + link.getTotalAttributeCount() - link.getAttributes().size(), + link.getSpanContext().isRemote()); } private SpanLinkMarshaler( @@ -65,7 +68,8 @@ private SpanLinkMarshaler( TraceFlags traceFlags, byte[] traceStateUtf8, KeyValueMarshaler[] attributeMarshalers, - int droppedAttributesCount) { + int droppedAttributesCount, + boolean isLinkContextRemote) { super( calculateSize( traceId, @@ -73,13 +77,15 @@ private SpanLinkMarshaler( traceFlags, traceStateUtf8, attributeMarshalers, - droppedAttributesCount)); + droppedAttributesCount, + isLinkContextRemote)); this.traceId = traceId; this.spanId = spanId; this.traceFlags = traceFlags; this.traceStateUtf8 = traceStateUtf8; this.attributeMarshalers = attributeMarshalers; this.droppedAttributesCount = droppedAttributesCount; + this.isLinkContextRemote = isLinkContextRemote; } @Override @@ -89,7 +95,8 @@ public void writeTo(Serializer output) throws IOException { output.serializeString(Span.Link.TRACE_STATE, traceStateUtf8); output.serializeRepeatedMessage(Span.Link.ATTRIBUTES, attributeMarshalers); output.serializeUInt32(Span.Link.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); - output.serializeByteAsFixed32(Span.Link.FLAGS, traceFlags.asByte()); + output.serializeFixed32( + Span.Link.FLAGS, traceFlags.withParentIsRemoteFlags(isLinkContextRemote)); } private static int calculateSize( @@ -98,14 +105,17 @@ private static int calculateSize( TraceFlags flags, byte[] traceStateUtf8, KeyValueMarshaler[] attributeMarshalers, - int droppedAttributesCount) { + int droppedAttributesCount, + boolean isLinkContextRemote) { int size = 0; size += MarshalerUtil.sizeTraceId(Span.Link.TRACE_ID, traceId); size += MarshalerUtil.sizeSpanId(Span.Link.SPAN_ID, spanId); size += MarshalerUtil.sizeBytes(Span.Link.TRACE_STATE, traceStateUtf8); size += MarshalerUtil.sizeRepeatedMessage(Span.Link.ATTRIBUTES, attributeMarshalers); size += MarshalerUtil.sizeUInt32(Span.Link.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); - size += MarshalerUtil.sizeByteAsFixed32(Span.Link.FLAGS, flags.asByte()); + size += + MarshalerUtil.sizeFixed32( + Span.Link.FLAGS, flags.withParentIsRemoteFlags(isLinkContextRemote)); return size; } } diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanMarshaler.java index e2e83c0f129..a0ef7d42806 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/traces/SpanMarshaler.java @@ -39,6 +39,7 @@ final class SpanMarshaler extends MarshalerWithSize { private final int droppedLinksCount; private final SpanStatusMarshaler spanStatusMarshaler; private final TraceFlags flags; + private final boolean isParentContextRemote; // Because SpanMarshaler is always part of a repeated field, it cannot return "null". static SpanMarshaler create(SpanData spanData) { @@ -75,7 +76,8 @@ static SpanMarshaler create(SpanData spanData) { spanLinkMarshalers, spanData.getTotalRecordedLinks() - spanData.getLinks().size(), SpanStatusMarshaler.create(spanData.getStatus()), - spanData.getSpanContext().getTraceFlags()); + spanData.getSpanContext().getTraceFlags(), + spanData.getParentSpanContext().isRemote()); } private SpanMarshaler( @@ -94,7 +96,8 @@ private SpanMarshaler( SpanLinkMarshaler[] spanLinkMarshalers, int droppedLinksCount, SpanStatusMarshaler spanStatusMarshaler, - TraceFlags flags) { + TraceFlags flags, + boolean isParentContextRemote) { super( calculateSize( traceId, @@ -112,7 +115,8 @@ private SpanMarshaler( spanLinkMarshalers, droppedLinksCount, spanStatusMarshaler, - flags)); + flags, + isParentContextRemote)); this.traceId = traceId; this.spanId = spanId; this.traceStateUtf8 = traceStateUtf8; @@ -129,6 +133,7 @@ private SpanMarshaler( this.droppedLinksCount = droppedLinksCount; this.spanStatusMarshaler = spanStatusMarshaler; this.flags = flags; + this.isParentContextRemote = isParentContextRemote; } @Override @@ -154,7 +159,7 @@ public void writeTo(Serializer output) throws IOException { output.serializeUInt32(Span.DROPPED_LINKS_COUNT, droppedLinksCount); output.serializeMessage(Span.STATUS, spanStatusMarshaler); - output.serializeByteAsFixed32(Span.FLAGS, flags.asByte()); + output.serializeFixed32(Span.FLAGS, flags.withParentIsRemoteFlags(isParentContextRemote)); } private static int calculateSize( @@ -173,7 +178,8 @@ private static int calculateSize( SpanLinkMarshaler[] spanLinkMarshalers, int droppedLinksCount, SpanStatusMarshaler spanStatusMarshaler, - TraceFlags flags) { + TraceFlags flags, + boolean isParentContextRemote) { int size = 0; size += MarshalerUtil.sizeTraceId(Span.TRACE_ID, traceId); size += MarshalerUtil.sizeSpanId(Span.SPAN_ID, spanId); @@ -196,7 +202,8 @@ private static int calculateSize( size += MarshalerUtil.sizeUInt32(Span.DROPPED_LINKS_COUNT, droppedLinksCount); size += MarshalerUtil.sizeMessage(Span.STATUS, spanStatusMarshaler); - size += MarshalerUtil.sizeByteAsFixed32(Span.FLAGS, flags.asByte()); + size += + MarshalerUtil.sizeFixed32(Span.FLAGS, flags.withParentIsRemoteFlags(isParentContextRemote)); return size; } diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/traces/TraceRequestMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/traces/TraceRequestMarshalerTest.java index 6bc17a22109..03028f33767 100644 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/traces/TraceRequestMarshalerTest.java +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/traces/TraceRequestMarshalerTest.java @@ -56,7 +56,9 @@ class TraceRequestMarshalerTest { new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}; private static final String TRACE_ID = TraceId.fromBytes(TRACE_ID_BYTES); private static final byte[] SPAN_ID_BYTES = new byte[] {0, 0, 0, 0, 4, 3, 2, 1}; + private static final byte[] PARENT_SPAN_ID_BYTES = new byte[] {0, 0, 0, 0, 5, 6, 7, 8}; private static final String SPAN_ID = SpanId.fromBytes(SPAN_ID_BYTES); + private static final String PARENT_SPAN_ID = SpanId.fromBytes(PARENT_SPAN_ID_BYTES); private static final String TRACE_STATE_VALUE = "baz=qux,foo=bar"; private static final SpanContext SPAN_CONTEXT = SpanContext.create( @@ -65,6 +67,10 @@ class TraceRequestMarshalerTest { TraceFlags.getSampled(), TraceState.builder().put("foo", "bar").put("baz", "qux").build()); + private static final SpanContext PARENT_SPAN_CONTEXT = + SpanContext.createFromRemoteParent( + TRACE_ID, PARENT_SPAN_ID, TraceFlags.getSampled(), TraceState.builder().build()); + @Test void toProtoResourceSpans() { ResourceSpansMarshaler[] resourceSpansMarshalers = @@ -148,7 +154,8 @@ void toProtoSpan() { assertThat(protoSpan.getTraceId().toByteArray()).isEqualTo(TRACE_ID_BYTES); assertThat(protoSpan.getSpanId().toByteArray()).isEqualTo(SPAN_ID_BYTES); assertThat(protoSpan.getFlags()) - .isEqualTo(((int) SPAN_CONTEXT.getTraceFlags().asByte()) & 0x00ff); + .isEqualTo( + (SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) | TraceFlags.getHasParentIsRemote()); assertThat(protoSpan.getTraceState()).isEqualTo(TRACE_STATE_VALUE); assertThat(protoSpan.getParentSpanId().toByteArray()).isEqualTo(new byte[] {}); assertThat(protoSpan.getName()).isEqualTo("GET /api/endpoint"); @@ -227,7 +234,9 @@ void toProtoSpan() { Span.Link.newBuilder() .setTraceId(ByteString.copyFrom(TRACE_ID_BYTES)) .setSpanId(ByteString.copyFrom(SPAN_ID_BYTES)) - .setFlags(SPAN_CONTEXT.getTraceFlags().asByte()) + .setFlags( + (SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) + | TraceFlags.getHasParentIsRemote()) .setTraceState(encodeTraceState(SPAN_CONTEXT.getTraceState())) .build()); assertThat(protoSpan.getDroppedLinksCount()).isEqualTo(1); // 2 - 1 @@ -235,6 +244,37 @@ void toProtoSpan() { .isEqualTo(Status.newBuilder().setCode(STATUS_CODE_OK).build()); } + @Test + void toProtoSpan_withRemoteParent() { + Span protoSpan = + parse( + Span.getDefaultInstance(), + SpanMarshaler.create( + TestSpanData.builder() + .setHasEnded(true) + .setSpanContext(SPAN_CONTEXT) + .setParentSpanContext(PARENT_SPAN_CONTEXT) + .setName("GET /api/endpoint") + .setKind(SpanKind.SERVER) + .setStartEpochNanos(12345) + .setEndEpochNanos(12349) + .setStatus(StatusData.ok()) + .build())); + + assertThat(protoSpan.getTraceId().toByteArray()).isEqualTo(TRACE_ID_BYTES); + assertThat(protoSpan.getSpanId().toByteArray()).isEqualTo(SPAN_ID_BYTES); + assertThat(protoSpan.getFlags()) + .isEqualTo((SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) | TraceFlags.getParentIsRemote()); + assertThat(protoSpan.getTraceState()).isEqualTo(TRACE_STATE_VALUE); + assertThat(protoSpan.getParentSpanId().toByteArray()).isEqualTo(PARENT_SPAN_ID_BYTES); + assertThat(protoSpan.getName()).isEqualTo("GET /api/endpoint"); + assertThat(protoSpan.getKind()).isEqualTo(SPAN_KIND_SERVER); + assertThat(protoSpan.getStartTimeUnixNano()).isEqualTo(12345); + assertThat(protoSpan.getEndTimeUnixNano()).isEqualTo(12349); + assertThat(protoSpan.getStatus()) + .isEqualTo(Status.newBuilder().setCode(STATUS_CODE_OK).build()); + } + @Test void toProtoSpanKind() { assertThat(SpanMarshaler.toProtoSpanKind(SpanKind.INTERNAL)) @@ -318,11 +358,28 @@ void toProtoSpanLink_WithoutAttributes() { Span.Link.newBuilder() .setTraceId(ByteString.copyFrom(TRACE_ID_BYTES)) .setSpanId(ByteString.copyFrom(SPAN_ID_BYTES)) - .setFlags(SPAN_CONTEXT.getTraceFlags().asByte()) + .setFlags( + (SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) + | TraceFlags.getHasParentIsRemote()) .setTraceState(TRACE_STATE_VALUE) .build()); } + @Test + void toProtoSpanLink_WithRemoteContext() { + assertThat( + parse( + Span.Link.getDefaultInstance(), + SpanLinkMarshaler.create(LinkData.create(PARENT_SPAN_CONTEXT)))) + .isEqualTo( + Span.Link.newBuilder() + .setTraceId(ByteString.copyFrom(TRACE_ID_BYTES)) + .setSpanId(ByteString.copyFrom(PARENT_SPAN_ID_BYTES)) + .setFlags( + (SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) | TraceFlags.getParentIsRemote()) + .build()); + } + @Test void toProtoSpanLink_WithAttributes() { assertThat( @@ -335,7 +392,9 @@ void toProtoSpanLink_WithAttributes() { Span.Link.newBuilder() .setTraceId(ByteString.copyFrom(TRACE_ID_BYTES)) .setSpanId(ByteString.copyFrom(SPAN_ID_BYTES)) - .setFlags(SPAN_CONTEXT.getTraceFlags().asByte()) + .setFlags( + (SPAN_CONTEXT.getTraceFlags().asByte() & 0xff) + | TraceFlags.getHasParentIsRemote()) .setTraceState(TRACE_STATE_VALUE) .addAttributes( KeyValue.newBuilder()