Skip to content

Commit def014f

Browse files
committed
Merge master into
2 parents ea2e598 + b863ae5 commit def014f

26 files changed

Lines changed: 671 additions & 124 deletions

File tree

‎build-logic/tag-registry/src/main/kotlin/datadog/buildlogic/tagRegistry/KnownTagsEmitter.kt‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ object KnownTagsEmitter {
5959
fun otelNameC(ddName: String) = otelNameOfConst.getValue(ddName)
6060

6161
val keyOfOrder = reg.tags.map { it.ddName } // stable emit order
62+
// OpenTelemetry names that denote a different tag depending on the span's direction. keyOf
63+
// ignores direction (a shared Datadog name such as server.port resolves to its own tag);
64+
// directionalKeyOf(name, direction) switches on each one's tag per direction.
65+
val directional = sortedMapOf<String, MutableMap<TagConventions.Direction, String>>()
66+
for (t in reg.tags) {
67+
for ((direction, otel) in t.scopedOtelNames) {
68+
directional.getOrPut(otel) { mutableMapOf() }[direction] = t.ddName
69+
}
70+
}
6271
return buildString {
6372
// Public API first (name + encoded id couplets), so readers see the useful parts up top; the
6473
// serial ids and keyOf/resolver machinery follow below. Derivation is in the trailing comment.
@@ -95,6 +104,34 @@ object KnownTagsEmitter {
95104

96105
// Serial numbers (globalSerial per tag) — public, so a switch on KnownTagCodec.serialNum outside
97106
// this package (the resolver here, TagInterceptor in core) can name them as case labels.
107+
// Per-direction aliases: each OpenTelemetry name that names a tag on spans of one direction, as
108+
// that tag's id. Code that knows its direction writes in OpenTelemetry terms by id
109+
// (Outbound.SERVER_ADDRESS_ID is PEER_HOSTNAME_ID) without knowing the Datadog name, and
110+
// without a lookup. Direction-free renames are repeated in each, so one class covers a span.
111+
for (direction in TagConventions.Direction.entries) {
112+
val aliases = sortedMapOf<String, String>()
113+
for (t in reg.tags) {
114+
val otel = t.otelName ?: t.scopedOtelNames[direction] ?: continue
115+
aliases[otel] = t.ddName
116+
}
117+
if (aliases.isEmpty()) continue
118+
val holder = DIRECTION_CLASS.getValue(direction)
119+
appendLine(" /** OpenTelemetry names on ${DIRECTION_SPANS.getValue(direction)} spans, as the ids of the tags they name. */")
120+
appendLine(" public static final class $holder {")
121+
val local = HashSet<String>()
122+
for ((otel, ddName) in aliases) {
123+
var constant = withSuffix(sanitize(otel), "_ID")
124+
var n = 2
125+
while (!local.add(constant)) constant = "${withSuffix(sanitize(otel), "_ID")}_${n++}"
126+
appendLine(" /** {@code ${escape(otel)}}: {@code ${escape(ddName)}}. */")
127+
appendLine(" public static final long $constant = $className.${idC(ddName)};")
128+
}
129+
appendLine()
130+
appendLine(" private $holder() {}")
131+
appendLine(" }")
132+
appendLine()
133+
}
134+
98135
appendLine(" // ---- serial numbers ----")
99136
for (t in reg.tags) {
100137
appendLine(" public static final int ${serialC(t.ddName)} = ${t.serial};")
@@ -221,6 +258,30 @@ object KnownTagsEmitter {
221258
int slot = StringIndex.EmbeddingSupport.indexOf(KEYOF_HASHES, KEYOF_KEYS, name);
222259
return slot < 0 ? 0L : KEYOF_IDS[slot];
223260
}
261+
262+
@Override
263+
public long directionalKeyOf(String name, int direction) {
264+
switch (name) {
265+
""".trimIndent()
266+
)
267+
for ((name, byDirection) in directional) {
268+
appendLine(" case \"${escape(name)}\":")
269+
appendLine(" switch (direction) {")
270+
for (d in TagConventions.Direction.entries) {
271+
val tag = byDirection[d] ?: continue
272+
appendLine(" case KnownTagCodec.DIRECTION_${d.name}:")
273+
appendLine(" return ${idC(tag)};")
274+
}
275+
appendLine(" default:")
276+
appendLine(" return 0L;")
277+
appendLine(" }")
278+
}
279+
appendLine(
280+
"""
281+
default:
282+
return 0L;
283+
}
284+
}
224285
};
225286
226287
private $className() {}
@@ -230,6 +291,20 @@ object KnownTagsEmitter {
230291
}
231292
}
232293

294+
private val DIRECTION_CLASS =
295+
mapOf(
296+
TagConventions.Direction.INBOUND to "Inbound",
297+
TagConventions.Direction.OUTBOUND to "Outbound",
298+
TagConventions.Direction.NONE to "Internal",
299+
)
300+
301+
private val DIRECTION_SPANS =
302+
mapOf(
303+
TagConventions.Direction.INBOUND to "server and consumer",
304+
TagConventions.Direction.OUTBOUND to "client and producer",
305+
TagConventions.Direction.NONE to "internal",
306+
)
307+
233308
private fun hex(id: Long): String = "0x%016XL".format(Locale.ROOT, id)
234309

235310
private fun escape(value: String): String = String(JsonStringEncoder.getInstance().quoteAsString(value))

‎build-logic/tag-registry/src/main/kotlin/datadog/buildlogic/tagRegistry/TagConventions.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class TagConventions private constructor(
6161
val name: String
6262
get() = identity.label
6363

64-
/** The Datadog-namespace name, shared by the declarations of a name declared per direction. */
64+
/** The Datadog-namespace name, shared by the tags of a name declared per direction. */
6565
val ddName: String
6666
get() = identity.ddName
6767
}

‎build-logic/tag-registry/src/main/kotlin/datadog/buildlogic/tagRegistry/TagRegistry.kt‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,32 @@ class TagRegistry private constructor(val tags: List<Tag>) {
106106
}
107107

108108
validateOtelNames(tags)
109-
return TagRegistry(tags)
109+
return TagRegistry(claimSharedNames(tags))
110+
}
111+
112+
/**
113+
* A tag whose Datadog name is also another tag's direction-scoped OpenTelemetry name keeps that
114+
* name in every direction the other tag doesn't claim it: `server.port` is its own tag (the
115+
* server's port), except on outbound spans, where it is `peer.port`. The Datadog name still
116+
* resolves to the tag without a direction; only OpenTelemetry-side resolution by direction maps
117+
* it to the other tag.
118+
*/
119+
private fun claimSharedNames(tags: List<Tag>): List<Tag> {
120+
val claimedBy = HashMap<String, MutableSet<TagConventions.Direction>>()
121+
for (t in tags) {
122+
for ((direction, otel) in t.scopedOtelNames) {
123+
claimedBy.getOrPut(otel) { mutableSetOf() }.add(direction)
124+
}
125+
}
126+
return tags.map { t ->
127+
val claimed = claimedBy[t.ddName] ?: return@map t
128+
require(t.otelName == null && t.scopedOtelNames.isEmpty()) {
129+
"'${t.name}' is another tag's OpenTelemetry name on ${claimed.joinToString { it.yamlKey }} " +
130+
"spans, so it cannot be renamed itself"
131+
}
132+
val own = TagConventions.Direction.entries.filter { it !in claimed }.associateWith { t.ddName }
133+
t.copy(scopedOtelNames = own.toSortedMap())
134+
}
110135
}
111136

112137
/** Requirement levels whose tags may share a slot with each other when a span type overflows. */
@@ -207,10 +232,10 @@ class TagRegistry private constructor(val tags: List<Tag>) {
207232
private fun strongestRequirement(levels: List<String>): String = levels.minBy { REQUIREMENT_STRENGTH.indexOf(it).let { i -> if (i < 0) Int.MAX_VALUE else i } }
208233

209234
/**
210-
* Rejects OpenTelemetry names that collide with any Datadog name. Within each direction,
211-
* a rename must also belong to a single tag. Direction-free renames reserve their name
212-
* in every direction. Scoped renames may share a name across directions: `server.address`
213-
* maps to `http.hostname` inbound and `peer.hostname` outbound.
235+
* Within each direction, a rename must belong to a single tag. A direction-free rename can't
236+
* be a Datadog name too, since without a direction its name would resolve to two tags. A scoped
237+
* rename may share a name across directions -- `server.address` is `http.hostname` inbound and
238+
* `peer.hostname` outbound -- and may be another tag's Datadog name (see [claimSharedNames]).
214239
*/
215240
private fun validateOtelNames(tags: List<Tag>) {
216241
val canonical = tags.map { it.ddName }.toSet()
@@ -219,7 +244,7 @@ class TagRegistry private constructor(val tags: List<Tag>) {
219244
val byDirection =
220245
t.otelName?.let { otel -> TagConventions.Direction.entries.associateWith { otel } } ?: t.scopedOtelNames
221246
for ((direction, otel) in byDirection) {
222-
require(otel !in canonical) {
247+
require(otel != t.ddName && (t.otelName == null || otel !in canonical)) {
223248
"OpenTelemetry name '$otel' (of '${t.name}') collides with canonical tag name '$otel'"
224249
}
225250
val prev = owner.put(direction to otel, t.name)

‎build-logic/tag-registry/src/main/kotlin/datadog/buildlogic/tagRegistry/TagRegistryGenerator.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ object TagRegistryGenerator {
103103
104104
# DIRECTION-SCOPED OPENTELEMETRY NAMES. Each applies only on spans of the given direction, so
105105
# it is not in the tables above: a tag is emitted under it when its span has that direction,
106-
# but name resolution does not use it until it knows the direction.
106+
# and a value written under it is resolved to the tag once the span's direction is known.
107107
""".trimIndent()
108108
)
109109
val scoped =

‎build-logic/tag-registry/src/test/kotlin/datadog/buildlogic/tagRegistry/TagRegistryGeneratorPluginTest.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ class TagRegistryGeneratorPluginTest {
249249
String nameOf(long id);
250250
String openTelemetryNameOf(long id, int direction);
251251
long keyOf(String name);
252+
long directionalKeyOf(String name, int direction);
252253
}
253254
public static final int DIRECTION_UNKNOWN = -1;
254255
public static final int DIRECTION_INBOUND = 0;

‎build-logic/tag-registry/src/test/kotlin/datadog/buildlogic/tagRegistry/TagRegistryGeneratorTest.kt‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,85 @@ class TagRegistryGeneratorTest {
340340
assertThat(conventions.resolve("http.server").map { it.name }).contains("peer.port@inbound")
341341
}
342342

343+
@Test
344+
fun `a Datadog name can be another tag's OpenTelemetry name in some directions`() {
345+
val domain =
346+
"""
347+
span_types:
348+
http.server:
349+
span-kind: server
350+
tags: [{dd-name: server.port, type: int}]
351+
http.client: {span-kind: client, include: [outbound_peer]}
352+
mixins:
353+
outbound_peer: {span-kind: client, tags: [{dd-name: peer.port, type: int, otel-name: server.port}]}
354+
"""
355+
val output = File(directory, "generated")
356+
357+
TagRegistryGenerator.generate(directory.conventionsFile(domain), output)
358+
359+
val tags = TagRegistry.build(tagConventions(domain)).tags.associateBy { it.name }
360+
assertThat(tags.getValue("server.port").scopedOtelNames)
361+
.isEqualTo(
362+
mapOf(TagConventions.Direction.INBOUND to "server.port", TagConventions.Direction.NONE to "server.port")
363+
)
364+
val source = contents(output).getValue("java/datadog/trace/api/KnownTags.java")
365+
assertThat(source.substringAfter("case \"server.port\":").substringBefore("default:"))
366+
.contains(
367+
"case KnownTagCodec.DIRECTION_INBOUND:",
368+
"return SERVER_PORT_ID;",
369+
"case KnownTagCodec.DIRECTION_OUTBOUND:",
370+
"return PEER_PORT_ID;",
371+
"case KnownTagCodec.DIRECTION_NONE:",
372+
)
373+
// Without a direction the Datadog name is its own tag; only directionalKeyOf maps it elsewhere.
374+
assertThat(source.substringAfter("KEYOF_VALUES = {").substringBefore("};"))
375+
.contains("SERVER_PORT_ID,")
376+
.doesNotContain("SENTINEL")
377+
}
378+
379+
@Test
380+
fun `each direction gets its OpenTelemetry names as the ids of the tags they name`() {
381+
val yaml = directory.conventionsFile(
382+
"""
383+
span_types:
384+
base: {abstract: true, tags: [{dd-name: http.method, otel-name: http.request.method}]}
385+
http.server:
386+
span-kind: server
387+
tags:
388+
- {dd-name: http.hostname, otel-name: server.address}
389+
- {dd-name: server.port, type: int}
390+
http.client: {span-kind: client, include: [outbound_peer]}
391+
mixins:
392+
outbound_peer:
393+
span-kind: client
394+
tags:
395+
- {dd-name: peer.hostname, otel-name: server.address}
396+
- {dd-name: peer.port, type: int, otel-name: server.port}
397+
"""
398+
)
399+
val output = File(directory, "generated")
400+
401+
TagRegistryGenerator.generate(yaml, output)
402+
403+
val source = contents(output).getValue("java/datadog/trace/api/KnownTags.java")
404+
fun holder(name: String) = source.substringAfter("public static final class $name {").substringBefore("private $name() {}")
405+
assertThat(holder("Outbound"))
406+
.contains(
407+
"SERVER_ADDRESS_ID = KnownTags.PEER_HOSTNAME_ID;",
408+
"SERVER_PORT_ID = KnownTags.PEER_PORT_ID;",
409+
"HTTP_REQUEST_METHOD_ID = KnownTags.HTTP_METHOD_ID;",
410+
)
411+
assertThat(holder("Inbound"))
412+
.contains(
413+
"SERVER_ADDRESS_ID = KnownTags.HTTP_HOSTNAME_ID;",
414+
"SERVER_PORT_ID = KnownTags.SERVER_PORT_ID;",
415+
"HTTP_REQUEST_METHOD_ID = KnownTags.HTTP_METHOD_ID;",
416+
)
417+
assertThat(holder("Internal"))
418+
.contains("SERVER_PORT_ID = KnownTags.SERVER_PORT_ID;")
419+
.doesNotContain("SERVER_ADDRESS_ID")
420+
}
421+
343422
@Test
344423
fun `a Datadog name declared per direction is one tag`() {
345424
val yaml = directory.conventionsFile(
@@ -434,6 +513,8 @@ class TagRegistryGeneratorTest {
434513
@TableTest(
435514
"""
436515
scenario | domain | message
516+
scoped rename to own name | span_types: {a: {span-kind: client, tags: [{dd-name: x, otel-name: x}]}} | collides with canonical
517+
shared name renamed itself | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o}]}, b: {span-kind: client, tags: [{dd-name: y, otel-name: x}]}} | so it cannot be renamed itself
437518
same name in one direction | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o}]}, b: {span-kind: consumer, tags: [{dd-name: y, otel-name: o}]}} | claimed by both 'x' and 'y' on inbound spans
438519
neutral contradicts scoped | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o, span-kind-neutral: true}]}, b: {span-kind: client, tags: [{dd-name: y, otel-name: o}]}} | on outbound spans
439520
map-form otel-name | span_types: {a: {span-kind: client, tags: [{dd-name: x, otel-name: {outbound: o}}]}} | invalid otel-name

‎build.gradle.kts‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import com.diffplug.gradle.spotless.GroovyExtension
2-
import com.diffplug.gradle.spotless.SpotlessExtension
32
import datadog.gradle.plugin.HostPlatform
43
import datadog.gradle.plugin.ci.testAggregate
54

@@ -32,12 +31,7 @@ val isCI = providers.environmentVariable("CI")
3231
apply(from = rootDir.resolve("gradle/repositories.gradle"))
3332
apply(from = rootDir.resolve("gradle/ddprof-override.gradle"))
3433

35-
spotless {
36-
// only resolve the spotless dependencies once in the build
37-
predeclareDeps()
38-
}
39-
40-
with(extensions["spotlessPredeclare"] as SpotlessExtension) {
34+
spotlessPredeclare {
4135
// these need to align with the types and versions in gradle/spotless.gradle
4236
java {
4337
removeUnusedImports()

‎dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelConventions.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static java.util.Collections.emptyMap;
1919
import static java.util.Locale.ROOT;
2020

21+
import datadog.trace.api.KnownTagCodec;
2122
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
2223
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
2324
import datadog.trace.bootstrap.instrumentation.api.Tags;
@@ -48,6 +49,18 @@ public final class OtelConventions {
4849

4950
private OtelConventions() {}
5051

52+
/**
53+
* The tag to store an attribute under on spans of {@code direction}. A direction-scoped
54+
* OpenTelemetry name is stored as the Datadog tag it denotes there: {@code server.address} is
55+
* {@code peer.hostname} on a client span and {@code http.hostname} on a server span. Every other
56+
* name, and a direction-scoped one that denotes no tag in {@code direction}, is stored as
57+
* written.
58+
*/
59+
static String attributeTag(String key, int direction) {
60+
long tagId = KnownTagCodec.directionalKeyOf(key, direction);
61+
return tagId == 0 ? key : KnownTagCodec.nameOf(tagId);
62+
}
63+
5164
/**
5265
* Convert OpenTelemetry {@link SpanKind} to {@link Tags#SPAN_KIND} value.
5366
*

‎dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelSpan.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44
import static datadog.opentelemetry.shim.trace.OtelConventions.applyNamingConvention;
55
import static datadog.opentelemetry.shim.trace.OtelConventions.applyReservedAttribute;
66
import static datadog.opentelemetry.shim.trace.OtelConventions.applySpanEventExceptionAttributes;
7+
import static datadog.opentelemetry.shim.trace.OtelConventions.attributeTag;
78
import static datadog.opentelemetry.shim.trace.OtelConventions.convertEventAttributes;
89
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
910
import static io.opentelemetry.api.trace.StatusCode.ERROR;
1011
import static io.opentelemetry.api.trace.StatusCode.OK;
1112
import static io.opentelemetry.api.trace.StatusCode.UNSET;
1213

1314
import datadog.context.ContextScope;
15+
import datadog.trace.api.KnownTagCodec;
1416
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1517
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
1618
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
1719
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
1820
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
1921
import datadog.trace.bootstrap.instrumentation.api.SpanWrapper;
22+
import datadog.trace.bootstrap.instrumentation.api.Tags;
2023
import datadog.trace.bootstrap.instrumentation.api.WithAgentSpan;
2124
import io.opentelemetry.api.common.AttributeKey;
2225
import io.opentelemetry.api.common.Attributes;
@@ -33,11 +36,17 @@
3336
@ParametersAreNonnullByDefault
3437
public class OtelSpan implements Span, WithAgentSpan, SpanWrapper {
3538
private final AgentSpan delegate;
39+
40+
/** The direction of the span's kind, fixed at start; it decides direction-scoped names. */
41+
private final int direction;
42+
3643
private StatusCode statusCode = UNSET;
3744
private volatile boolean recording = true;
3845

3946
public OtelSpan(AgentSpan delegate) {
4047
this.delegate = delegate;
48+
Object kind = delegate.getTag(Tags.SPAN_KIND);
49+
this.direction = KnownTagCodec.directionOfKind(kind == null ? null : kind.toString());
4150
if (delegate instanceof AttachableWrapper) {
4251
((AttachableWrapper) delegate).attachWrapper(this);
4352
}
@@ -69,7 +78,7 @@ public <T> Span setAttribute(AttributeKey<T> key, T value) {
6978
}
7079
break;
7180
default:
72-
this.delegate.setTag(key.getKey(), value);
81+
this.delegate.setTag(attributeTag(key.getKey(), this.direction), value);
7382
break;
7483
}
7584
}

0 commit comments

Comments
 (0)