From bcd239592df4b427b0a43e6c3cdcde7fba41f0aa Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:55:12 -0400 Subject: [PATCH 1/6] [SVLS-8245] Add tests for named pipe name rules --- .../java/com/datadog/NamedPipeNamesTest.java | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/test/java/com/datadog/NamedPipeNamesTest.java diff --git a/src/test/java/com/datadog/NamedPipeNamesTest.java b/src/test/java/com/datadog/NamedPipeNamesTest.java new file mode 100644 index 0000000..5523169 --- /dev/null +++ b/src/test/java/com/datadog/NamedPipeNamesTest.java @@ -0,0 +1,176 @@ +package com.datadog; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NamedPipeNamesTest { + + private static String repeat(char c, int count) { + StringBuilder builder = new StringBuilder(count); + for (int i = 0; i < count; i++) { + builder.append(c); + } + return builder.toString(); + } + + @Test + void choose_windowsPipeNamesWin() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", "apm_windows"); + env.put("DD_DOGSTATSD_WINDOWS_PIPE_NAME", "dogstatsd_windows"); + + NamedPipeNames names = NamedPipeNames.choose(env, new Properties()); + + assertEquals("apm_windows", names.tracePipeName); + assertEquals("dogstatsd_windows", names.dogstatsdPipeName); + assertTrue(names.warnings.isEmpty()); + } + + @Test + void choose_windowsPipeNamesWinOverCustomerSetNames() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", "apm_windows"); + env.put("DD_DOGSTATSD_WINDOWS_PIPE_NAME", "dogstatsd_windows"); + env.put("DD_TRACE_PIPE_NAME", "trace_env"); + Properties systemProperties = new Properties(); + systemProperties.setProperty("dd.dogstatsd.pipe.name", "dogstatsd_property"); + + NamedPipeNames names = NamedPipeNames.choose(env, systemProperties); + + assertEquals("apm_windows", names.tracePipeName); + assertEquals("dogstatsd_windows", names.dogstatsdPipeName); + } + + @Test + void choose_usesCustomerSetEnvironmentVariables() { + Map env = new HashMap<>(); + env.put("DD_TRACE_PIPE_NAME", "trace_env"); + env.put("DD_DOGSTATSD_PIPE_NAME", "dogstatsd_env"); + + NamedPipeNames names = NamedPipeNames.choose(env, new Properties()); + + assertEquals("trace_env", names.tracePipeName); + assertEquals("dogstatsd_env", names.dogstatsdPipeName); + assertTrue(names.warnings.isEmpty()); + } + + @Test + void choose_usesCustomerSetJvmOptions() { + Properties systemProperties = new Properties(); + systemProperties.setProperty("dd.trace.pipe.name", "trace_property"); + systemProperties.setProperty("dd.dogstatsd.pipe.name", "dogstatsd_property"); + + NamedPipeNames names = NamedPipeNames.choose(new HashMap<>(), systemProperties); + + assertEquals("trace_property", names.tracePipeName); + assertEquals("dogstatsd_property", names.dogstatsdPipeName); + assertTrue(names.warnings.isEmpty()); + } + + @Test + void choose_jvmOptionsWinOverEnvironmentVariablesLikeTheTracer() { + Map env = new HashMap<>(); + env.put("DD_TRACE_PIPE_NAME", "trace_env"); + env.put("DD_DOGSTATSD_PIPE_NAME", "dogstatsd_env"); + Properties systemProperties = new Properties(); + systemProperties.setProperty("dd.trace.pipe.name", "trace_property"); + systemProperties.setProperty("dd.dogstatsd.pipe.name", "dogstatsd_property"); + + NamedPipeNames names = NamedPipeNames.choose(env, systemProperties); + + assertEquals("trace_property", names.tracePipeName); + assertEquals("dogstatsd_property", names.dogstatsdPipeName); + } + + @Test + void choose_generatesNamesWhenNoneAreSet() { + NamedPipeNames names = NamedPipeNames.choose(new HashMap<>(), new Properties()); + + assertTrue(names.tracePipeName.matches("dd_trace_[0-9a-f]{32}"), names.tracePipeName); + assertTrue(names.dogstatsdPipeName.matches("dd_dogstatsd_[0-9a-f]{32}"), names.dogstatsdPipeName); + assertTrue(names.warnings.isEmpty()); + } + + @Test + void choose_generatesDifferentNamesEachTime() { + NamedPipeNames first = NamedPipeNames.choose(new HashMap<>(), new Properties()); + NamedPipeNames second = NamedPipeNames.choose(new HashMap<>(), new Properties()); + + assertNotEquals(first.tracePipeName, second.tracePipeName); + assertNotEquals(first.dogstatsdPipeName, second.dogstatsdPipeName); + } + + @Test + void choose_treatsEmptyValuesAsUnset() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", ""); + env.put("DD_TRACE_PIPE_NAME", "trace_env"); + env.put("DD_DOGSTATSD_PIPE_NAME", ""); + + NamedPipeNames names = NamedPipeNames.choose(env, new Properties()); + + assertEquals("trace_env", names.tracePipeName); + assertTrue(names.dogstatsdPipeName.matches("dd_dogstatsd_[0-9a-f]{32}"), names.dogstatsdPipeName); + } + + @Test + void choose_warnsWhenCustomerSetNamesDifferFromWindowsPipeNames() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", "apm_windows"); + env.put("DD_DOGSTATSD_WINDOWS_PIPE_NAME", "dogstatsd_windows"); + env.put("DD_TRACE_PIPE_NAME", "trace_env"); + Properties systemProperties = new Properties(); + systemProperties.setProperty("dd.dogstatsd.pipe.name", "dogstatsd_property"); + + NamedPipeNames names = NamedPipeNames.choose(env, systemProperties); + + assertEquals(2, names.warnings.size(), names.warnings.toString()); + assertTrue(names.warnings.get(0).contains("DD_TRACE_PIPE_NAME"), names.warnings.get(0)); + assertTrue(names.warnings.get(0).contains("DD_APM_WINDOWS_PIPE_NAME"), names.warnings.get(0)); + assertTrue(names.warnings.get(1).contains("dd.dogstatsd.pipe.name"), names.warnings.get(1)); + assertTrue(names.warnings.get(1).contains("DD_DOGSTATSD_WINDOWS_PIPE_NAME"), names.warnings.get(1)); + } + + @Test + void choose_doesNotWarnWhenCustomerSetNamesMatchWindowsPipeNames() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", "apm_windows"); + env.put("DD_DOGSTATSD_WINDOWS_PIPE_NAME", "dogstatsd_windows"); + env.put("DD_TRACE_PIPE_NAME", "apm_windows"); + env.put("DD_DOGSTATSD_PIPE_NAME", "dogstatsd_windows"); + + NamedPipeNames names = NamedPipeNames.choose(env, new Properties()); + + assertTrue(names.warnings.isEmpty(), names.warnings.toString()); + } + + @Test + void choose_warnsWhenNamesAreLongerThan256Characters() { + Map env = new HashMap<>(); + env.put("DD_APM_WINDOWS_PIPE_NAME", repeat('a', 257)); + Properties systemProperties = new Properties(); + systemProperties.setProperty("dd.dogstatsd.pipe.name", repeat('b', 300)); + + NamedPipeNames names = NamedPipeNames.choose(env, systemProperties); + + assertEquals(2, names.warnings.size(), names.warnings.toString()); + assertTrue(names.warnings.get(0).contains("DD_APM_WINDOWS_PIPE_NAME"), names.warnings.get(0)); + assertTrue(names.warnings.get(0).contains("257"), names.warnings.get(0)); + assertTrue(names.warnings.get(1).contains("dd.dogstatsd.pipe.name"), names.warnings.get(1)); + assertTrue(names.warnings.get(1).contains("300"), names.warnings.get(1)); + } + + @Test + void choose_doesNotWarnFor256CharacterNames() { + Map env = new HashMap<>(); + env.put("DD_TRACE_PIPE_NAME", repeat('a', 256)); + + NamedPipeNames names = NamedPipeNames.choose(env, new Properties()); + + assertTrue(names.warnings.isEmpty(), names.warnings.toString()); + } +} From bfee86f4b2cca6f0a3a30c072f9d5e07a8919010 Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:55:42 -0400 Subject: [PATCH 2/6] [SVLS-8245] Add helper that chooses per-function pipe names --- src/main/java/com/datadog/NamedPipeNames.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/main/java/com/datadog/NamedPipeNames.java diff --git a/src/main/java/com/datadog/NamedPipeNames.java b/src/main/java/com/datadog/NamedPipeNames.java new file mode 100644 index 0000000..1237f79 --- /dev/null +++ b/src/main/java/com/datadog/NamedPipeNames.java @@ -0,0 +1,87 @@ +package com.datadog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; + +/** + * Chooses the trace and DogStatsD pipe names for one function, following the .NET + * compatibility layer: the binary's *_WINDOWS_PIPE_NAME setting, then a name the customer + * already set for the tracer, then a generated per-function name. + */ +final class NamedPipeNames { + static final int MAX_PIPE_NAME_LENGTH = 256; + + static final String TRACE_WINDOWS_PIPE_NAME_ENV = "DD_APM_WINDOWS_PIPE_NAME"; + static final String TRACE_PIPE_NAME_ENV = "DD_TRACE_PIPE_NAME"; + static final String TRACE_PIPE_NAME_PROPERTY = "dd.trace.pipe.name"; + + static final String DOGSTATSD_WINDOWS_PIPE_NAME_ENV = "DD_DOGSTATSD_WINDOWS_PIPE_NAME"; + static final String DOGSTATSD_PIPE_NAME_ENV = "DD_DOGSTATSD_PIPE_NAME"; + static final String DOGSTATSD_PIPE_NAME_PROPERTY = "dd.dogstatsd.pipe.name"; + + final String tracePipeName; + final String dogstatsdPipeName; + final List warnings; + + private NamedPipeNames(String tracePipeName, String dogstatsdPipeName, List warnings) { + this.tracePipeName = tracePipeName; + this.dogstatsdPipeName = dogstatsdPipeName; + this.warnings = Collections.unmodifiableList(warnings); + } + + static NamedPipeNames choose(Map env, Properties systemProperties) { + List warnings = new ArrayList<>(); + String tracePipeName = choosePipeName(env, systemProperties, TRACE_WINDOWS_PIPE_NAME_ENV, + TRACE_PIPE_NAME_PROPERTY, TRACE_PIPE_NAME_ENV, "dd_trace_", warnings); + String dogstatsdPipeName = choosePipeName(env, systemProperties, DOGSTATSD_WINDOWS_PIPE_NAME_ENV, + DOGSTATSD_PIPE_NAME_PROPERTY, DOGSTATSD_PIPE_NAME_ENV, "dd_dogstatsd_", warnings); + return new NamedPipeNames(tracePipeName, dogstatsdPipeName, warnings); + } + + private static String choosePipeName(Map env, Properties systemProperties, + String windowsPipeNameEnv, String pipeNameProperty, String pipeNameEnv, + String generatedPrefix, List warnings) { + // The tracer reads system properties before environment variables, so a JVM option + // the customer passed is the customer-set name when both are present. + String customerSetName = nonEmpty(systemProperties.getProperty(pipeNameProperty)); + String customerSetSource = pipeNameProperty; + if (customerSetName == null) { + customerSetName = nonEmpty(env.get(pipeNameEnv)); + customerSetSource = pipeNameEnv; + } + + String windowsPipeName = nonEmpty(env.get(windowsPipeNameEnv)); + if (windowsPipeName != null) { + if (customerSetName != null && !customerSetName.equals(windowsPipeName)) { + warnings.add(String.format("%s ('%s') differs from %s ('%s'). Using %s.", + customerSetSource, customerSetName, windowsPipeNameEnv, windowsPipeName, + windowsPipeNameEnv)); + } + warnIfTooLong(windowsPipeNameEnv, windowsPipeName, warnings); + return windowsPipeName; + } + + if (customerSetName != null) { + warnIfTooLong(customerSetSource, customerSetName, warnings); + return customerSetName; + } + + return generatedPrefix + UUID.randomUUID().toString().replace("-", ""); + } + + private static void warnIfTooLong(String source, String pipeName, List warnings) { + if (pipeName.length() > MAX_PIPE_NAME_LENGTH) { + warnings.add(String.format( + "%s is %d characters long. Windows pipe names can be at most %d characters.", + source, pipeName.length(), MAX_PIPE_NAME_LENGTH)); + } + } + + private static String nonEmpty(String value) { + return value == null || value.isEmpty() ? null : value; + } +} From c69b429ad1eb0f9b1510dd6907bbd8a5d101da46 Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:56:14 -0400 Subject: [PATCH 3/6] [SVLS-8245] Add tests for the dd-java-agent version gate --- .../com/datadog/TracerVersionGateTest.java | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/test/java/com/datadog/TracerVersionGateTest.java diff --git a/src/test/java/com/datadog/TracerVersionGateTest.java b/src/test/java/com/datadog/TracerVersionGateTest.java new file mode 100644 index 0000000..9af6fff --- /dev/null +++ b/src/test/java/com/datadog/TracerVersionGateTest.java @@ -0,0 +1,173 @@ +package com.datadog; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class TracerVersionGateTest { + private static final String MINIMUM = "1.68.0"; + private static final String TRACER_PREMAIN_CLASS = "datadog.trace.bootstrap.AgentPreCheck"; + + @TempDir + Path tempDir; + + private Path writeAgentJar(String fileName, String premainClass, String implementationVersion) + throws IOException { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + if (premainClass != null) { + attributes.putValue("Premain-Class", premainClass); + } + if (implementationVersion != null) { + attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, implementationVersion); + } + Path jar = tempDir.resolve(fileName); + try (OutputStream out = Files.newOutputStream(jar); + JarOutputStream jarOut = new JarOutputStream(out, manifest)) { + // A manifest is all the gate reads. + } + return jar; + } + + private Path writeTracerJar(String implementationVersion) throws IOException { + return writeAgentJar("dd-java-agent.jar", TRACER_PREMAIN_CLASS, implementationVersion); + } + + private static String javaagent(Path jar) { + return "-javaagent:" + jar.toAbsolutePath(); + } + + @ParameterizedTest + @ValueSource(strings = {"1.68.0", "1.68.1", "1.69.0", "1.100.0", "2.0.0"}) + void check_passesForMinimumVersionOrLater(String version) throws IOException { + Path tracer = writeTracerJar(version); + + TracerVersionGate gate = TracerVersionGate.check( + Collections.singletonList(javaagent(tracer)), MINIMUM); + + assertTrue(gate.passed, gate.reason); + } + + @ParameterizedTest + @ValueSource(strings = {"1.67.1", "1.67.0", "1.9.9", "0.99.0", "1.67.0-SNAPSHOT"}) + void check_failsForOlderVersions(String version) throws IOException { + Path tracer = writeTracerJar(version); + + TracerVersionGate gate = TracerVersionGate.check( + Collections.singletonList(javaagent(tracer)), MINIMUM); + + assertFalse(gate.passed); + assertTrue(gate.reason.contains(version), gate.reason); + assertTrue(gate.reason.contains(MINIMUM), gate.reason); + } + + @ParameterizedTest + @ValueSource(strings = {"1.68.0-SNAPSHOT", "1.68.0-105-gefa6bd64", "1.68.0-SNAPSHOT-DIRTY"}) + void check_passesForDevBuildsOfMinimumVersion(String version) throws IOException { + Path tracer = writeTracerJar(version); + + TracerVersionGate gate = TracerVersionGate.check( + Collections.singletonList(javaagent(tracer)), MINIMUM); + + assertTrue(gate.passed, gate.reason); + } + + @Test + void check_usesDefaultMinimumVersion() throws IOException { + Path tracer = writeTracerJar(TracerVersionGate.MINIMUM_TRACER_VERSION); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(tracer))); + + assertTrue(gate.passed, gate.reason); + } + + @Test + void check_findsTracerByPremainClassNotFileName() throws IOException { + Path compat = writeAgentJar("dd-java-agent-lookalike.jar", "com.datadog.ServerlessCompatAgent", "9.9.9"); + Path tracer = writeAgentJar("renamed-tracer.jar", TRACER_PREMAIN_CLASS, "1.68.0"); + + TracerVersionGate gate = TracerVersionGate.check( + Arrays.asList("-Xmx512m", javaagent(compat), javaagent(tracer)), MINIMUM); + + assertTrue(gate.passed, gate.reason); + } + + @Test + void check_ignoresAgentOptions() throws IOException { + Path tracer = writeTracerJar("1.68.0"); + + TracerVersionGate gate = TracerVersionGate.check( + Collections.singletonList(javaagent(tracer) + "=some=option"), MINIMUM); + + assertTrue(gate.passed, gate.reason); + } + + @Test + void check_failsWhenNoJavaagentIsPresent() { + TracerVersionGate gate = TracerVersionGate.check(Arrays.asList("-Xmx512m", "-Dfoo=bar"), MINIMUM); + + assertFalse(gate.passed); + assertTrue(gate.reason.contains("dd-java-agent"), gate.reason); + } + + @Test + void check_failsWhenOnlyOtherAgentsArePresent() throws IOException { + Path compat = writeAgentJar("dd-java-agent.jar", "com.datadog.ServerlessCompatAgent", "1.68.0"); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(compat)), MINIMUM); + + assertFalse(gate.passed); + } + + @Test + void check_failsWhenTracerJarDoesNotExist() { + Path missing = tempDir.resolve("dd-java-agent.jar"); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(missing)), MINIMUM); + + assertFalse(gate.passed); + } + + @Test + void check_failsWhenJavaagentIsNotAJar() throws IOException { + Path notAJar = tempDir.resolve("dd-java-agent.jar"); + Files.write(notAJar, "not a jar".getBytes("UTF-8")); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(notAJar)), MINIMUM); + + assertFalse(gate.passed); + } + + @Test + void check_failsWhenTracerVersionIsMissing() throws IOException { + Path tracer = writeTracerJar(null); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(tracer)), MINIMUM); + + assertFalse(gate.passed); + assertTrue(gate.reason.contains("Implementation-Version"), gate.reason); + } + + @ParameterizedTest + @ValueSource(strings = {"unknown", "1.68", "v1.68.0", ""}) + void check_failsWhenTracerVersionIsUnreadable(String version) throws IOException { + Path tracer = writeTracerJar(version); + + TracerVersionGate gate = TracerVersionGate.check(Collections.singletonList(javaagent(tracer)), MINIMUM); + + assertFalse(gate.passed); + } +} From 8134982e3afd09219c43033b443af983484ccc74 Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:57:23 -0400 Subject: [PATCH 4/6] [SVLS-8245] Add dd-java-agent version gate for named pipes --- .../java/com/datadog/TracerVersionGate.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/main/java/com/datadog/TracerVersionGate.java diff --git a/src/main/java/com/datadog/TracerVersionGate.java b/src/main/java/com/datadog/TracerVersionGate.java new file mode 100644 index 0000000..56f96d0 --- /dev/null +++ b/src/main/java/com/datadog/TracerVersionGate.java @@ -0,0 +1,131 @@ +package com.datadog; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Decides whether the dd-java-agent in this JVM supports named pipes. Older tracers hang + * uploading traces over a pipe and never send their own metrics over one, so pipe names are + * only handed out when the tracer is at least {@link #MINIMUM_TRACER_VERSION}. + */ +final class TracerVersionGate { + static final String TRACER_PREMAIN_CLASS = "datadog.trace.bootstrap.AgentPreCheck"; + + // Placeholder: the next unreleased dd-trace-java version on master. Replace it with the + // first dd-trace-java release that includes the named-pipe transport and StatsD fixes. + static final String MINIMUM_TRACER_VERSION = "1.68.0"; + + private static final String JAVAAGENT_PREFIX = "-javaagent:"; + private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(\\D.*)?$"); + + final boolean passed; + final String reason; + + private TracerVersionGate(boolean passed, String reason) { + this.passed = passed; + this.reason = reason; + } + + static TracerVersionGate check() { + List jvmArguments; + try { + jvmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); + } catch (Exception e) { + return new TracerVersionGate(false, "unable to read JVM arguments to find dd-java-agent: " + e); + } + return check(jvmArguments); + } + + static TracerVersionGate check(List jvmArguments) { + return check(jvmArguments, MINIMUM_TRACER_VERSION); + } + + static TracerVersionGate check(List jvmArguments, String minimumVersion) { + int[] minimum = parseVersion(minimumVersion); + if (minimum == null) { + return new TracerVersionGate(false, "invalid minimum dd-java-agent version " + minimumVersion); + } + + for (String argument : jvmArguments) { + if (!argument.startsWith(JAVAAGENT_PREFIX)) { + continue; + } + String jarPath = javaagentJarPath(argument); + Manifest manifest = readManifest(jarPath); + if (manifest == null) { + continue; + } + Attributes attributes = manifest.getMainAttributes(); + if (!TRACER_PREMAIN_CLASS.equals(attributes.getValue("Premain-Class"))) { + continue; + } + + String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (version == null || version.isEmpty()) { + return new TracerVersionGate(false, + "dd-java-agent at " + jarPath + " has no Implementation-Version in its manifest"); + } + int[] tracer = parseVersion(version); + if (tracer == null) { + return new TracerVersionGate(false, + "unable to read dd-java-agent version '" + version + "' at " + jarPath); + } + if (compare(tracer, minimum) < 0) { + return new TracerVersionGate(false, "dd-java-agent " + version + " at " + jarPath + + " is older than " + minimumVersion + ", the first version that supports named pipes"); + } + return new TracerVersionGate(true, "dd-java-agent " + version + " at " + jarPath + + " supports named pipes (minimum " + minimumVersion + ")"); + } + + return new TracerVersionGate(false, + "no readable dd-java-agent found among -javaagent options (Premain-Class " + TRACER_PREMAIN_CLASS + ")"); + } + + // The JVM treats everything after the first '=' as agent options. + private static String javaagentJarPath(String argument) { + String value = argument.substring(JAVAAGENT_PREFIX.length()); + int optionsStart = value.indexOf('='); + return optionsStart >= 0 ? value.substring(0, optionsStart) : value; + } + + private static Manifest readManifest(String jarPath) { + try (JarFile jarFile = new JarFile(jarPath, false)) { + return jarFile.getManifest(); + } catch (IOException | RuntimeException e) { + return null; + } + } + + // Reads major.minor.patch and ignores any suffix, so 1.68.0-SNAPSHOT counts as 1.68.0. + private static int[] parseVersion(String version) { + Matcher matcher = VERSION_PATTERN.matcher(version); + if (!matcher.matches()) { + return null; + } + try { + return new int[] { + Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)), + Integer.parseInt(matcher.group(3)) + }; + } catch (NumberFormatException e) { + return null; + } + } + + private static int compare(int[] left, int[] right) { + for (int i = 0; i < left.length; i++) { + if (left[i] != right[i]) { + return Integer.compare(left[i], right[i]); + } + } + return 0; + } +} From 726abc26ac37453f9fb03b17338a172944c56526 Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:57:46 -0400 Subject: [PATCH 5/6] [SVLS-8245] Set tracer pipe system properties on Windows Azure Functions --- .../com/datadog/ServerlessCompatAgent.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/com/datadog/ServerlessCompatAgent.java b/src/main/java/com/datadog/ServerlessCompatAgent.java index 97abbae..d36d7af 100644 --- a/src/main/java/com/datadog/ServerlessCompatAgent.java +++ b/src/main/java/com/datadog/ServerlessCompatAgent.java @@ -106,6 +106,34 @@ public static boolean isAzureFlexWithoutDDAzureResourceGroup() { && System.getenv("DD_AZURE_RESOURCE_GROUP") == null; } + /** + * Gives this function its own named pipes when dd-java-agent supports them. The tracer reads + * these system properties before its environment variables, so they must be set before + * dd-java-agent's premain runs. When the gate fails, nothing is set and the tracer keeps + * whatever it inherited: TCP and UDP by default, or the customer's own pipe settings. + * + * @return the chosen pipe names, or null when named pipes are off + */ + static NamedPipeNames configureNamedPipes() { + TracerVersionGate gate = TracerVersionGate.check(); + if (!gate.passed) { + log.info("Named pipes are off, keeping inherited trace and DogStatsD settings: {}", gate.reason); + return null; + } + log.debug("Named pipes are on: {}", gate.reason); + + NamedPipeNames pipeNames = NamedPipeNames.choose(System.getenv(), System.getProperties()); + for (String warning : pipeNames.warnings) { + log.warn(warning); + } + + System.setProperty(NamedPipeNames.TRACE_PIPE_NAME_PROPERTY, pipeNames.tracePipeName); + System.setProperty(NamedPipeNames.DOGSTATSD_PIPE_NAME_PROPERTY, pipeNames.dogstatsdPipeName); + log.info("Configured named pipes - Trace: {}, DogStatsD: {}", + pipeNames.tracePipeName, pipeNames.dogstatsdPipeName); + return pipeNames; + } + public static void premain(String agentArgs, Instrumentation instrumentation) { CloudEnvironment environment = getEnvironment(); log.debug("Environment detected: {}", environment); @@ -169,6 +197,10 @@ public static void premain(String agentArgs, Instrumentation instrumentation) { String packageVersion = getPackageVersion(); log.debug("Found package version {}", packageVersion); + if (environment == CloudEnvironment.AZURE_FUNCTION && isWindows()) { + configureNamedPipes(); + } + ProcessBuilder processBuilder = new ProcessBuilder(executableFile.getAbsolutePath()); processBuilder.environment().put("DD_SERVERLESS_COMPAT_VERSION", packageVersion); processBuilder.inheritIO(); From e30da1c20b36a6f9d32204a8dc94fcafa7d9c63e Mon Sep 17 00:00:00 2001 From: "E. Lewis" Date: Thu, 15 Oct 2026 16:58:41 -0400 Subject: [PATCH 6/6] [SVLS-8245] Pass pipe names to the binary's environment --- src/main/java/com/datadog/ServerlessCompatAgent.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/datadog/ServerlessCompatAgent.java b/src/main/java/com/datadog/ServerlessCompatAgent.java index d36d7af..7ae512f 100644 --- a/src/main/java/com/datadog/ServerlessCompatAgent.java +++ b/src/main/java/com/datadog/ServerlessCompatAgent.java @@ -197,12 +197,18 @@ public static void premain(String agentArgs, Instrumentation instrumentation) { String packageVersion = getPackageVersion(); log.debug("Found package version {}", packageVersion); + NamedPipeNames pipeNames = null; if (environment == CloudEnvironment.AZURE_FUNCTION && isWindows()) { - configureNamedPipes(); + pipeNames = configureNamedPipes(); } ProcessBuilder processBuilder = new ProcessBuilder(executableFile.getAbsolutePath()); processBuilder.environment().put("DD_SERVERLESS_COMPAT_VERSION", packageVersion); + if (pipeNames != null) { + processBuilder.environment().put(NamedPipeNames.TRACE_WINDOWS_PIPE_NAME_ENV, pipeNames.tracePipeName); + processBuilder.environment().put(NamedPipeNames.DOGSTATSD_WINDOWS_PIPE_NAME_ENV, + pipeNames.dogstatsdPipeName); + } processBuilder.inheritIO(); processBuilder.start(); } catch (Exception e) {