diff --git a/internal-api/src/main/java/datadog/trace/api/ProcessTags.java b/internal-api/src/main/java/datadog/trace/api/ProcessTags.java index 6c060f3c2c8..f87d02012fd 100644 --- a/internal-api/src/main/java/datadog/trace/api/ProcessTags.java +++ b/internal-api/src/main/java/datadog/trace/api/ProcessTags.java @@ -5,9 +5,12 @@ import datadog.trace.util.TraceUtils; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +21,7 @@ public class ProcessTags { private static class Lazy { static final Map TAGS = loadTags(); static volatile UTF8BytesString serializedForm; + static volatile List listForm; private static Map loadTags() { Map tags = new LinkedHashMap<>(); @@ -81,15 +85,17 @@ private static void fillJbossTags(Map tags) { } } - static synchronized UTF8BytesString calculateSerializedForm() { - if (serializedForm == null && !TAGS.isEmpty()) { - serializedForm = - UTF8BytesString.create( - TAGS.entrySet().stream() - .map(entry -> entry.getKey() + ":" + TraceUtils.normalizeTag(entry.getValue())) - .collect(Collectors.joining(","))); + static void calculate() { + if (listForm != null || TAGS.isEmpty()) { + return; + } + synchronized (Lazy.TAGS) { + final Stream tagStream = + TAGS.entrySet().stream() + .map(entry -> entry.getKey() + ":" + TraceUtils.normalizeTag(entry.getValue())); + listForm = Collections.unmodifiableList(tagStream.collect(Collectors.toList())); + serializedForm = UTF8BytesString.create(String.join(",", listForm)); } - return serializedForm; } } @@ -100,7 +106,20 @@ public static synchronized void addTag(String key, String value) { if (enabled) { Lazy.TAGS.put(key, value); Lazy.serializedForm = null; + Lazy.listForm = null; + } + } + + public static List getTagsAsList() { + if (!enabled) { + return null; + } + final List listForm = Lazy.listForm; + if (listForm != null) { + return listForm; } + Lazy.calculate(); + return Lazy.listForm; } public static UTF8BytesString getTagsForSerialization() { @@ -111,13 +130,15 @@ public static UTF8BytesString getTagsForSerialization() { if (serializedForm != null) { return serializedForm; } - return Lazy.calculateSerializedForm(); + Lazy.calculate(); + return Lazy.serializedForm; } /** Visible for testing. */ static void empty() { Lazy.TAGS.clear(); Lazy.serializedForm = null; + Lazy.listForm = null; } /** Visible for testing. */ diff --git a/internal-api/src/test/groovy/datadog/trace/api/ProcessTagsForkedTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/ProcessTagsForkedTest.groovy index 7cecdec6cdd..d0835bfbf48 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ProcessTagsForkedTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ProcessTagsForkedTest.groovy @@ -72,6 +72,7 @@ class ProcessTagsForkedTest extends DDSpecification { ProcessTags.addTag("test", "value") then: assert ProcessTags.tagsForSerialization == null + assert ProcessTags.tagsAsList == null } def 'should lazily recalculate when a tag is added'() { @@ -80,12 +81,18 @@ class ProcessTagsForkedTest extends DDSpecification { ProcessTags.reset() when: def processTags = ProcessTags.tagsForSerialization + def tagsAsList = ProcessTags.tagsAsList then: assert ProcessTags.enabled assert processTags != null + assert tagsAsList != null + assert tagsAsList.size() > 0 when: ProcessTags.addTag("test", "value") then: assert ProcessTags.tagsForSerialization.toString() == "$processTags,test:value" + def size = ProcessTags.tagsAsList.size() + assert size == tagsAsList.size() + 1 + assert ProcessTags.tagsAsList[size - 1] == "test:value" } } diff --git a/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java b/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java index 65cc3b229dd..7fce349e7bf 100644 --- a/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java +++ b/remote-config/remote-config-core/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java @@ -1,6 +1,7 @@ package datadog.remoteconfig.tuf; import com.squareup.moshi.Json; +import datadog.trace.api.ProcessTags; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -26,7 +27,14 @@ public static RemoteConfigRequest newRequest( ClientInfo.TracerInfo tracerInfo = new RemoteConfigRequest.ClientInfo.TracerInfo( - runtimeId, tracerVersion, serviceName, extraServices, serviceEnv, serviceVersion, tags); + runtimeId, + tracerVersion, + serviceName, + extraServices, + serviceEnv, + serviceVersion, + tags, + ProcessTags.getTagsAsList()); ClientInfo clientInfo = new RemoteConfigRequest.ClientInfo( @@ -174,6 +182,9 @@ public static class TracerInfo { @Json(name = "app_version") private final String serviceVersion; + @Json(name = "process_tags") + private final List processTags; + public TracerInfo( String runtimeId, String tracerVersion, @@ -181,7 +192,8 @@ public TracerInfo( List extraServices, String serviceEnv, String serviceVersion, - List tags) { + List tags, + List processTags) { this.runtimeId = runtimeId; this.tracerVersion = tracerVersion; this.serviceName = serviceName; @@ -189,6 +201,7 @@ public TracerInfo( this.serviceEnv = serviceEnv; this.serviceVersion = serviceVersion; this.tags = tags; + this.processTags = processTags; } public String getServiceName() { @@ -210,6 +223,10 @@ public String getServiceVersion() { public List getTags() { return tags; } + + public List getProcessTags() { + return processTags; + } } private static class AgentInfo { diff --git a/remote-config/remote-config-core/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy b/remote-config/remote-config-core/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy index 5935d68cdb4..601beadd9e4 100644 --- a/remote-config/remote-config-core/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy +++ b/remote-config/remote-config-core/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy @@ -1,11 +1,15 @@ package datadog.remoteconfig +import com.squareup.moshi.Moshi import datadog.remoteconfig.tuf.RemoteConfigRequest +import datadog.trace.api.ProcessTags import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.DDSpecification import datadog.trace.api.Config import datadog.trace.api.remoteconfig.ServiceNameCollector +import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED + class PollerRequestFactoryTest extends DDSpecification { static final String TRACER_VERSION = "v1.2.3" @@ -51,4 +55,32 @@ class PollerRequestFactoryTest extends DDSpecification { then: request.client.tracerInfo.extraServices.contains(extraService) } + + void 'remote config provides process tags when enabled = #enabled'() { + setup: + if (enabled) { + injectSysConfig(EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED, "true") + } + ProcessTags.reset() + PollerRequestFactory factory = new PollerRequestFactory(Config.get(), TRACER_VERSION, CONTAINER_ID, ENTITY_ID, INVALID_REMOTE_CONFIG_URL, null) + + when: + def request = factory.buildRemoteConfigRequest( Collections.singletonList("ASM"), null, null, 0, ServiceNameCollector.get()) + def json = new Moshi.Builder().build().adapter(RemoteConfigRequest).toJson(request) + then: + def epName = request.client.tracerInfo.processTags.find {it =~ "entrypoint.name:.+"} + def workingDir = request.client.tracerInfo.processTags.find {it =~ "entrypoint.workdir:.+"} + + if (enabled) { + assert workingDir != null + assert epName != null + assert json.contains('"process_tags":[') + } else { + assert workingDir == null + assert epName == null + assert !json.contains('"process_tags":[') + } + where: + enabled << [true, false] + } }