diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index e00f162e464..fa3f3e4dd5a 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -174,7 +174,7 @@ def sharedShadowJar = tasks.register('sharedShadowJar', ShadowJar) { // Put the jar in a different directory so we don't overwrite the normal shadow jar and // break caching, and also to not interfere with CI scripts that copy everything in the // libs directory - it.destinationDirectory.set(file("${project.buildDir}/shared-lib")) + it.destinationDirectory.set(project.layout.buildDirectory.dir("shared-lib")) // Add a classifier so we don't confuse the jar file with the normal shadow jar archiveClassifier = 'shared' it.dependencies { @@ -192,7 +192,7 @@ includeShadowJar(sharedShadowJar, 'shared') // place the tracer in its own shadow jar separate to instrumentation def traceShadowJar = tasks.register('traceShadowJar', ShadowJar) { configurations = [project.configurations.traceShadowInclude] - it.destinationDirectory.set(file("${project.buildDir}/trace-lib")) + it.destinationDirectory.set(project.layout.buildDirectory.dir("trace-lib")) archiveClassifier = 'trace' it.dependencies deps.excludeShared } @@ -265,6 +265,9 @@ dependencies { testImplementation project(':dd-trace-core') testImplementation project(':utils:test-utils') + testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: libs.versions.okhttp.legacy.get() + testImplementation project(':utils:test-agent-utils:decoder') + testImplementation libs.bundles.test.logging testImplementation libs.guava testImplementation libs.okhttp diff --git a/dd-java-agent/src/main/java/datadog/trace/bootstrap/BootstrapInitializationTelemetry.java b/dd-java-agent/src/main/java/datadog/trace/bootstrap/BootstrapInitializationTelemetry.java index 627aa05606e..6e09fa0f30a 100644 --- a/dd-java-agent/src/main/java/datadog/trace/bootstrap/BootstrapInitializationTelemetry.java +++ b/dd-java-agent/src/main/java/datadog/trace/bootstrap/BootstrapInitializationTelemetry.java @@ -2,6 +2,7 @@ import datadog.json.JsonWriter; import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.Closeable; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -211,7 +212,8 @@ public ForwarderJsonSenderThread(String forwarderPath, byte[] payload) { public void run() { ProcessBuilder builder = new ProcessBuilder(forwarderPath, "library_entrypoint"); - try { + // Run forwarder and mute tracing for subprocesses executed in by dd-java-agent. + try (final Closeable ignored = muteTracing()) { Process process = builder.start(); try (OutputStream out = process.getOutputStream()) { out.write(payload); @@ -221,5 +223,19 @@ public void run() { System.err.println("Failed to send telemetry: " + e.getMessage()); } } + + @SuppressForbidden + private Closeable muteTracing() { + try { + Class agentTracerClass = + Class.forName("datadog.trace.bootstrap.instrumentation.api.AgentTracer"); + Object tracerAPI = agentTracerClass.getMethod("get").invoke(null); + Object scope = tracerAPI.getClass().getMethod("muteTracing").invoke(tracerAPI); + return (Closeable) scope; + } catch (Throwable e) { + // Ignore all exceptions and fallback to No-Op Closable. + return () -> {}; + } + } } } diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/AgentLoadedIntoBootstrapTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/AgentLoadedIntoBootstrapTest.groovy index fdd9d0f3f47..04e1d1c2361 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/AgentLoadedIntoBootstrapTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/AgentLoadedIntoBootstrapTest.groovy @@ -13,8 +13,8 @@ class AgentLoadedIntoBootstrapTest extends Specification { def "Agent loads in when separate jvm is launched"() { expect: IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName() - , "" as String[] - , "" as String[] + , [] + , [] , [:] , true) == 0 } @@ -28,8 +28,8 @@ class AgentLoadedIntoBootstrapTest extends Specification { expect: IntegrationTestUtils.runOnSeparateJvm(mainClassName - , "" as String[] - , "" as String[] + , [] + , [] , [:] , pathToJar as String , true) == 0 diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy index 3c0c1e910cd..db440efe415 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy @@ -20,8 +20,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddd.profiling.enabled=true", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -36,8 +36,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.profiling.enabled=true", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -52,8 +52,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.profiling.enabled=true", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djava.util.logging.manager=jvmbootstraptest.MissingLogManager" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -68,8 +68,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.profiling.enabled=true", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customlogmanager=true" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -84,8 +84,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.profiling.enabled=true", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customjmxbuilder=false" - ] as String[] - , "" as String[] + ] + , [] , ["JBOSS_HOME": "/"] , true) == 0 } @@ -102,8 +102,8 @@ class CustomLogManagerTest extends Specification { "-Ddd.app.customlogmanager=false", "-Ddd.app.customjmxbuilder=false", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager" - ] as String[] - , "" as String[] + ] + , [] , ["JBOSS_HOME": "/"] , true) == 0 } diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomMBeanServerBuilderTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomMBeanServerBuilderTest.groovy index d4b76fce833..de1a80bf163 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomMBeanServerBuilderTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomMBeanServerBuilderTest.groovy @@ -21,8 +21,8 @@ class CustomMBeanServerBuilderTest extends Specification { "-Ddd.profiling.enabled=true", "-Ddd.instrumentation.telemetry.enabled=false", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -38,8 +38,8 @@ class CustomMBeanServerBuilderTest extends Specification { "-Ddd.instrumentation.telemetry.enabled=false", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djavax.management.builder.initial=jvmbootstraptest.CustomMBeanServerBuilder" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -55,8 +55,8 @@ class CustomMBeanServerBuilderTest extends Specification { "-Ddd.instrumentation.telemetry.enabled=false", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djavax.management.builder.initial=jvmbootstraptest.MissingMBeanServerBuilder" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -72,8 +72,8 @@ class CustomMBeanServerBuilderTest extends Specification { "-Ddd.instrumentation.telemetry.enabled=false", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customjmxbuilder=true" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -90,8 +90,8 @@ class CustomMBeanServerBuilderTest extends Specification { "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customjmxbuilder=false", "-Djavax.management.builder.initial=jvmbootstraptest.CustomMBeanServerBuilder" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/InitializationTelemetryTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/InitializationTelemetryTest.groovy index e990a2226fa..607b4c74be0 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/InitializationTelemetryTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/InitializationTelemetryTest.groovy @@ -1,11 +1,11 @@ package datadog.trace.agent -import spock.lang.Specification -import spock.lang.Timeout -import spock.lang.IgnoreIf +import datadog.test.SimpleAgentMock import datadog.trace.api.Platform - import jvmbootstraptest.InitializationTelemetryCheck +import spock.lang.IgnoreIf +import spock.lang.Specification +import spock.lang.Timeout @Timeout(30) @IgnoreIf(reason = "SecurityManager is permanently disabled as of JDK 24", value = { @@ -26,11 +26,31 @@ class InitializationTelemetryTest extends Specification { def "normal start-up"() { when: - def result = InitializationTelemetryCheck.runTestJvm(null, false, "sleep") + def result = InitializationTelemetryCheck.runTestJvm(null) + + then: + result.exitCode == 0 + result.telemetryJson.contains('library_entrypoint.complete') + } + + def "test initial telemetry forwarder trace muted"() { + when: + def agent = new SimpleAgentMock().start() + def result = InitializationTelemetryCheck.runTestJvm(null, agent.port) then: result.exitCode == 0 result.telemetryJson.contains('library_entrypoint.complete') + + // Check that we have only one span related to sub-process execution, + // and it is not initial telemetry forwarder. + agent.spans.size() == 1 + def span = agent.spans.get(0) + span.name == 'command_execution' + span.resource == 'echo' + + cleanup: + agent.close() } def "incomplete agent start-up"() { @@ -38,7 +58,7 @@ class InitializationTelemetryTest extends Specification { // agent initialization to fail. However, we should catch the exception allowing the application // to run normally. when: - def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockByteBuddy, false, "sleep") + def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockByteBuddy) then: result.exitCode == 0 @@ -50,7 +70,7 @@ class InitializationTelemetryTest extends Specification { // In this case, the SecurityManager blocks access to the forwarder environment variable, // so the tracer is unable to report initialization telemetry when: - def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockForwarderEnvVar, true) + def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockForwarderEnvVar) then: result.exitCode == 0 @@ -62,7 +82,7 @@ class InitializationTelemetryTest extends Specification { // In this case, the SecurityManager blocks access to process execution, so the tracer is // unable to invoke the forwarder executable when: - def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockForwarderExecution, true) + def result = InitializationTelemetryCheck.runTestJvm(InitializationTelemetryCheck.BlockForwarderExecution) then: result.exitCode == 0 diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/InstrumenterUnloadTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/InstrumenterUnloadTest.groovy index 09097926279..140d64902e1 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/InstrumenterUnloadTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/InstrumenterUnloadTest.groovy @@ -21,8 +21,8 @@ class InstrumenterUnloadTest extends Specification { , [ "-verbose:class", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL" - ] as String[] - , "" as String[] + ] + , [] , ["DD_API_KEY": API_KEY] , new PrintStream(testOutput)) diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy index e436fcf8762..86d36b4d1f6 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy @@ -28,8 +28,8 @@ class JMXFetchTest extends Specification { "-Ddd.jmxfetch.start-delay=0", "-Ddd.jmxfetch.statsd.port=${jmxStatsSocket.localPort}", "-Ddd.writer.type=DDAgentWriter" - ] as String[] - , ["30000"] as String[] + ] + , ["30000"] , [:] , System.getProperty("java.class.path")) @@ -64,8 +64,8 @@ class JMXFetchTest extends Specification { "-Ddd.jmxfetch.start-delay=0", "-Ddd.jmxfetch.statsd.host=example.local", "-Ddd.writer.type=DDAgentWriter" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) @@ -89,8 +89,8 @@ class JMXFetchTest extends Specification { "-Ddd.trace.debug=true", "-Ddd.writer.type=DDAgentWriter" ] - + configSettings as String[] - , "" as String[] + + configSettings as List + , [] , [:] , new PrintStream(testOutput)) diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/ListIntegrationsTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/ListIntegrationsTest.groovy index 2ed27903838..12f609ff3d5 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/ListIntegrationsTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/ListIntegrationsTest.groovy @@ -13,8 +13,8 @@ class ListIntegrationsTest extends Specification { def testOutput = new ByteArrayOutputStream() expect: IntegrationTestUtils.runOnSeparateJvm(AgentJar.name - , [] as String[] - , ["-li"] as String[] + , [] + , ["-li"] , [:] , new PrintStream(testOutput)) == 0 !new String(testOutput.toByteArray()).contains("ERROR") diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/LogLevelTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/LogLevelTest.groovy index 029e61f7fdd..c5b17c7f46d 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/LogLevelTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/LogLevelTest.groovy @@ -21,8 +21,8 @@ class LogLevelTest extends Specification { "-Ddd.trace.debug=false", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 1 } @@ -35,8 +35,8 @@ class LogLevelTest extends Specification { "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=debug", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -48,8 +48,8 @@ class LogLevelTest extends Specification { "-Ddd.trace.debug=false", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , ["DD_TRACE_DEBUG": "true"] , true) == 1 } @@ -61,8 +61,8 @@ class LogLevelTest extends Specification { "-Ddd.trace.debug=true", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , [:] , true) == 0 } @@ -71,8 +71,8 @@ class LogLevelTest extends Specification { def "DD_TRACE_DEBUG is true"() { expect: IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false"] as String[] - , "" as String[] + , ["-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false"] + , [] , ["DD_TRACE_DEBUG": "true"] , true) == 0 } @@ -84,8 +84,8 @@ class LogLevelTest extends Specification { "-Ddd.trace.debug=true", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , ["DD_TRACE_DEBUG": "false"] , true) == 0 } @@ -98,8 +98,8 @@ class LogLevelTest extends Specification { "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=debug", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , ["DD_TRACE_DEBUG": "false"] , true) == 0 } @@ -111,8 +111,8 @@ class LogLevelTest extends Specification { "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=info", "-Ddd.jmxfetch.enabled=false", "-Ddd.trace.enabled=false" - ] as String[] - , "" as String[] + ] + , [] , ["DD_TRACE_DEBUG": "true"] , true) == 1 } diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy index bcdccfc5d1d..8ca457850bd 100644 --- a/dd-java-agent/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy +++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy @@ -121,8 +121,8 @@ class ClassLoadingTest extends Specification { def "classloader instrumentation works with isolated classloaders"() { expect: IntegrationTestUtils.runOnSeparateJvm(IsolatedClassloading.getName() - , ["-Ddd.trace.debug=false", "-Ddd.jmxfetch.enabled=false"] as String[] - , "" as String[] + , ["-Ddd.trace.debug=false", "-Ddd.jmxfetch.enabled=false"] + , [] , [:] , true) == 0 } diff --git a/dd-java-agent/src/test/java/datadog/test/SimpleAgentMock.java b/dd-java-agent/src/test/java/datadog/test/SimpleAgentMock.java new file mode 100644 index 00000000000..e7e336d4163 --- /dev/null +++ b/dd-java-agent/src/test/java/datadog/test/SimpleAgentMock.java @@ -0,0 +1,67 @@ +package datadog.test; + +import datadog.trace.test.agent.decoder.DecodedMessage; +import datadog.trace.test.agent.decoder.DecodedSpan; +import datadog.trace.test.agent.decoder.DecodedTrace; +import datadog.trace.test.agent.decoder.Decoder; +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +/** + * Simple web server to mock responses from agent and collect spans. We can not use `TestHttpServer` + * from `:dd-java-agent:testing` because it will bring unwanted dependencies into classpath, and we + * are trying to make sure that we expose as little as possible into the bootstrap and system class + * loaders. + */ +public class SimpleAgentMock implements Closeable { + private static final MockResponse EMPTY_200_RESPONSE = new MockResponse().setResponseCode(200); + + private final MockWebServer server; + private final List spans = new CopyOnWriteArrayList<>(); + + public SimpleAgentMock() { + server = new MockWebServer(); + + server.setDispatcher( + new Dispatcher() { + @Override + public MockResponse dispatch(final RecordedRequest request) { + if ("/v0.4/traces".equals(request.getPath())) { + byte[] body = request.getBody().readByteArray(); + + DecodedMessage message = Decoder.decodeV04(body); + for (DecodedTrace trace : message.getTraces()) { + spans.addAll(trace.getSpans()); + } + } + + return EMPTY_200_RESPONSE; + } + }); + } + + public SimpleAgentMock start() throws IOException { + server.start(); + + return this; + } + + public int getPort() { + return server.getPort(); + } + + public List getSpans() { + return spans; + } + + @Override + public void close() throws IOException { + server.shutdown(); + } +} diff --git a/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java b/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java index 07703a35bce..ec84b9f13c8 100644 --- a/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java +++ b/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java @@ -2,6 +2,7 @@ import static datadog.trace.test.util.ForkedTestUtils.getMaxMemoryArgumentForFork; import static datadog.trace.test.util.ForkedTestUtils.getMinMemoryArgumentForFork; +import static java.util.stream.Collectors.toList; import datadog.trace.bootstrap.BootstrapProxy; import java.io.BufferedReader; @@ -14,9 +15,9 @@ import java.lang.management.RuntimeMXBean; import java.lang.reflect.Field; import java.net.URL; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; @@ -68,7 +69,7 @@ public static URL createJarWithClasses(final Class... classes) throws IOExcep * @param mainClassName The name of the class to use for Main-Class and Premain-Class. May be null * @param classes classes to package into the jar. * @return the location of the newly created jar. - * @throws IOException + * @throws IOException if an I/O error occurs. */ public static File createJarFileWithClasses(final String mainClassName, final Class... classes) throws IOException { @@ -144,7 +145,8 @@ public static String[] getBootstrapPackagePrefixes() throws Exception { private static String getAgentArgument() { final RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); for (final String arg : runtimeMxBean.getInputArguments()) { - if (arg.startsWith("-javaagent")) { + // Pick only `dd-java-agent` and exclude IDE debugger agent. + if (arg.startsWith("-javaagent") && arg.contains("dd-java-agent")) { return arg; } } @@ -158,8 +160,8 @@ private static String getAgentJarLocation() { public static int runOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final boolean printOutputStreams) throws Exception { @@ -169,8 +171,8 @@ public static int runOnSeparateJvm( public static int runOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final PrintStream out) throws Exception { @@ -180,8 +182,8 @@ public static int runOnSeparateJvm( public static int runOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final File classpath, final boolean printOutputStreams) @@ -192,8 +194,8 @@ public static int runOnSeparateJvm( public static int runOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final String classpath, final boolean printOutputStreams) @@ -213,12 +215,12 @@ public static int runOnSeparateJvm( * @param mainClassName The name of the entry point class. Must declare a main method. * @param out Optional stream to print the stdout and stderr of the child jvm * @return the return code of the child jvm - * @throws Exception + * @throws Exception If failed to run on separate JVM. */ public static int runOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final String classpath, final PrintStream out) @@ -241,15 +243,17 @@ public static int runOnSeparateJvm( public static Process startOnSeparateJvm( final String mainClassName, - final String[] jvmArgs, - final String[] mainMethodArgs, + final List jvmArgs, + final List mainMethodArgs, final Map envVars, final String classpath) throws Exception { - final String separator = System.getProperty("file.separator"); + final String separator = FileSystems.getDefault().getSeparator(); final String path = System.getProperty("java.home") + separator + "bin" + separator + "java"; - final List vmArgsList = new ArrayList<>(Arrays.asList(jvmArgs)); + // Groovy may produce a mix of `String` and `GString` instances. + // To ensure consistent handling, map all values to plain `String`: + final List vmArgsList = jvmArgs.stream().map(CharSequence::toString).collect(toList()); boolean runAsJar = "datadog.trace.bootstrap.AgentJar".equals(mainClassName); @@ -271,7 +275,7 @@ public static Process startOnSeparateJvm( commands.add(classpath); commands.add(mainClassName); } - commands.addAll(Arrays.asList(mainMethodArgs)); + commands.addAll(mainMethodArgs); final ProcessBuilder processBuilder = new ProcessBuilder(commands.toArray(new String[0])); processBuilder.environment().putAll(envVars); @@ -280,21 +284,9 @@ public static Process startOnSeparateJvm( private static void waitFor(final Process process, final long timeout, final TimeUnit unit) throws InterruptedException, TimeoutException { - final long startTime = System.nanoTime(); - long rem = unit.toNanos(timeout); - - do { - try { - process.exitValue(); - return; - } catch (final IllegalThreadStateException ex) { - if (rem > 0) { - Thread.sleep(Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100)); - } - } - rem = unit.toNanos(timeout) - (System.nanoTime() - startTime); - } while (rem > 0); - throw new TimeoutException(); + if (!process.waitFor(timeout, unit)) { + throw new TimeoutException(); + } } private static class StreamGobbler extends Thread { diff --git a/dd-java-agent/src/test/java/jvmbootstraptest/InitializationTelemetryCheck.java b/dd-java-agent/src/test/java/jvmbootstraptest/InitializationTelemetryCheck.java index 23d1f1bec2a..14427750b81 100644 --- a/dd-java-agent/src/test/java/jvmbootstraptest/InitializationTelemetryCheck.java +++ b/dd-java-agent/src/test/java/jvmbootstraptest/InitializationTelemetryCheck.java @@ -1,5 +1,7 @@ package jvmbootstraptest; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_AGENT_PORT; + import datadog.trace.agent.test.IntegrationTestUtils; import java.io.File; import java.io.FilePermission; @@ -9,10 +11,14 @@ import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Basic sanity check that InitializationTelemetry is functioning @@ -20,12 +26,19 @@ *

Checks edge cases where InitializationTelemetry is blocked by SecurityManagers */ public class InitializationTelemetryCheck { - public static void main(String[] args) throws InterruptedException { + public static void main(String[] args) throws Exception { // Emulates the real application performing work in main(). - // That should give enough time to send initial telemetry from daemon thread. - if (args.length > 0 && "sleep".equals(args[0])) { - Thread.sleep(1000); + // Start sub-process to generate one trace. + try { + ProcessBuilder builder = new ProcessBuilder("echo"); + Process process = builder.start(); + process.waitFor(5, TimeUnit.SECONDS); + } catch (SecurityException se) { + // Ignore security exceptions, as it can be part of strict security manager test. } + + // That should give enough time to send initial telemetry and traces. + Thread.sleep(2000); } /** Blocks the loading of the agent bootstrap */ @@ -77,20 +90,11 @@ protected boolean checkFileExecutePermission(FilePermission perm, Object ctx, St public static final Result runTestJvm(Class securityManagerClass) throws Exception { - return runTestJvm(securityManagerClass, false, null); - } - - public static final Result runTestJvm( - Class securityManagerClass, boolean printStreams) - throws Exception { - return runTestJvm(securityManagerClass, printStreams, null); + return runTestJvm(securityManagerClass, DEFAULT_TRACE_AGENT_PORT); } public static final Result runTestJvm( - Class securityManagerClass, - boolean printStreams, - String mainArgs) - throws Exception { + Class securityManagerClass, int port) throws Exception { File jarFile = IntegrationTestUtils.createJarFileWithClasses(requiredClasses(securityManagerClass)); @@ -99,6 +103,12 @@ public static final Result runTestJvm( createTempFile("forwarder", "sh", PosixFilePermissions.fromString("rwxr--r--")); File outputFile = new File(forwarderFile.getAbsoluteFile() + ".out"); + List jvmArgs = new ArrayList<>(); + jvmArgs.add("-Ddd.trace.agent.port=" + port); + if (securityManagerClass != null) { + jvmArgs.add("-Djava.security.manager=" + securityManagerClass.getName()); + } + write( forwarderFile, "#!/usr/bin/env bash\n", @@ -108,11 +118,11 @@ public static final Result runTestJvm( int exitCode = IntegrationTestUtils.runOnSeparateJvm( InitializationTelemetryCheck.class.getName(), - InitializationTelemetryCheck.jvmArgs(securityManagerClass), - InitializationTelemetryCheck.mainArgs(mainArgs), + jvmArgs, + Collections.emptyList(), InitializationTelemetryCheck.envVars(forwarderFile), jarFile, - printStreams); + true); return new Result(exitCode, read(outputFile)); } finally { @@ -168,22 +178,6 @@ public static final Class[] requiredClasses( } } - public static final String[] jvmArgs(Class securityManagerClass) { - if (securityManagerClass == null) { - return new String[] {}; - } else { - return new String[] {"-Djava.security.manager=" + securityManagerClass.getName()}; - } - } - - public static final String[] mainArgs(String args) { - if (args == null) { - return new String[] {}; - } else { - return args.split(","); - } - } - public static final Map envVars(File forwarderFile) { Map envVars = new HashMap<>(); envVars.put("DD_TELEMETRY_FORWARDER_PATH", forwarderFile.getAbsolutePath()); diff --git a/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java b/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java index 9d7b2ad1b26..4e0711dda24 100644 --- a/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java +++ b/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java @@ -12,7 +12,7 @@ public static void main(final String... args) throws Exception { if (System.getProperty("dd.app.customlogmanager") != null) { System.out.println("dd.app.customlogmanager != null"); - if (Boolean.valueOf(System.getProperty("dd.app.customlogmanager"))) { + if (Boolean.parseBoolean(System.getProperty("dd.app.customlogmanager"))) { System.setProperty("java.util.logging.manager", CUSTOM_LOG_MANAGER_CLASS_NAME); customAssert( LogManager.getLogManager().getClass(), diff --git a/dd-java-agent/src/test/java/jvmbootstraptest/SecurityManagerCheck.java b/dd-java-agent/src/test/java/jvmbootstraptest/SecurityManagerCheck.java index 938fa46d72c..bbaf2a0ef10 100644 --- a/dd-java-agent/src/test/java/jvmbootstraptest/SecurityManagerCheck.java +++ b/dd-java-agent/src/test/java/jvmbootstraptest/SecurityManagerCheck.java @@ -3,6 +3,7 @@ import datadog.trace.agent.test.IntegrationTestUtils; import java.io.File; import java.util.Collections; +import java.util.List; import java.util.Map; public final class SecurityManagerCheck { @@ -41,12 +42,13 @@ public static final Class[] requiredClasses( }; } - public static final String[] jvmArgs(Class securityManagerClass) { - return new String[] {"-Djava.security.manager=" + securityManagerClass.getName()}; + public static final List jvmArgs( + Class securityManagerClass) { + return Collections.singletonList("-Djava.security.manager=" + securityManagerClass.getName()); } - public static final String[] mainArgs() { - return new String[] {}; + public static final List mainArgs() { + return Collections.emptyList(); } public static final Map envVars() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 66bfdeacc2e..33e570fbd88 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -169,7 +169,7 @@ public static CoreTracerBuilder builder() { /** Default service name if none provided on the trace or span */ final String serviceName; - /** Writer is an charge of reporting traces and spans to the desired endpoint */ + /** Writer is in charge of reporting traces and spans to the desired endpoint */ final Writer writer; /** Sampler defines the sampling policy in order to reduce the number of traces for instance */