Skip to content

Provide access to VM arguments via shared CLIHelper #8538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions components/cli/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("me.champeau.jmh")
}

apply(from = "$rootDir/gradle/java.gradle")

jmh {
version = "1.28"
}
88 changes: 88 additions & 0 deletions components/cli/src/main/java/datadog/cli/CLIHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package datadog.cli;

import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.io.BufferedReader;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

public final class CLIHelper {
private static final List<String> VM_ARGS = findVmArgs();

public static List<String> getVmArgs() {
return VM_ARGS;
}

@SuppressForbidden
private static List<String> findVmArgs() {
// Try ProcFS on Linux
try {
if (isLinux()) {
Path cmdlinePath = Paths.get("/proc/self/cmdline");
if (Files.exists(cmdlinePath)) {
try (BufferedReader in = Files.newBufferedReader(cmdlinePath)) {
return Arrays.asList(in.readLine().split("\0"));
}
}
}
} catch (Throwable ignored) {
// Ignored exception
}

// Try Oracle-based
// IBM Semeru Runtime 1.8.0_345-b01 will throw UnsatisfiedLinkError here.
try {
final Class<?> managementFactoryHelperClass =
Class.forName("sun.management.ManagementFactoryHelper");

final Class<?> vmManagementClass = Class.forName("sun.management.VMManagement");

Object vmManagement;

try {
vmManagement =
managementFactoryHelperClass.getDeclaredMethod("getVMManagement").invoke(null);
} catch (final NoSuchMethodException e) {
// Older vm before getVMManagement() existed
final Field field = managementFactoryHelperClass.getDeclaredField("jvm");
field.setAccessible(true);
vmManagement = field.get(null);
field.setAccessible(false);
}

//noinspection unchecked
return (List<String>) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
} catch (final ReflectiveOperationException | UnsatisfiedLinkError ignored) {
// Ignored exception
}

// Try IBM-based.
try {
final Class<?> VMClass = Class.forName("com.ibm.oti.vm.VM");
final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null);
return Arrays.asList(argArray);
} catch (final ReflectiveOperationException ignored) {
// Ignored exception
}

// Fallback to default
try {
return ManagementFactory.getRuntimeMXBean().getInputArguments();
} catch (final Throwable t) {
// Throws InvocationTargetException on modularized applications
// with non-opened java.management module
System.err.println("WARNING: Unable to get VM args using managed beans");
}
return Collections.emptyList();
}

private static boolean isLinux() {
return System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public final class Constants {
"datadog.slf4j",
"datadog.json",
"datadog.context",
"datadog.cli",
"datadog.appsec.api",
"datadog.trace.api",
"datadog.trace.bootstrap",
Expand Down
1 change: 1 addition & 0 deletions dd-java-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ tasks.withType(GenerateMavenPom).configureEach { task ->

dependencies {
implementation project(path: ':components:json')
implementation project(path: ':components:cli')
modules {
module("com.squareup.okio:okio") {
replacedBy("com.datadoghq.okio:okio") // embed our patched fork
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@

import static java.nio.charset.StandardCharsets.UTF_8;

import datadog.cli.CLIHelper;
import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
Expand Down Expand Up @@ -383,7 +381,7 @@ private static List<File> getAgentFilesFromVMArguments() {
// - On IBM-based JDKs since at least 1.7
// This prevents custom log managers from working correctly
// Use reflection to bypass the loading of the class~
for (final String argument : getVMArgumentsThroughReflection()) {
for (final String argument : CLIHelper.getVmArgs()) {
if (argument.startsWith(JAVA_AGENT_ARGUMENT)) {
int index = argument.indexOf('=', JAVA_AGENT_ARGUMENT.length());
String agentPathname =
Expand Down Expand Up @@ -424,57 +422,6 @@ private static File getAgentFileUsingClassLoaderLookup() throws URISyntaxExcepti
return javaagentFile;
}

@SuppressForbidden
private static List<String> getVMArgumentsThroughReflection() {
// Try Oracle-based
// IBM Semeru Runtime 1.8.0_345-b01 will throw UnsatisfiedLinkError here.
try {
final Class<?> managementFactoryHelperClass =
Class.forName("sun.management.ManagementFactoryHelper");

final Class<?> vmManagementClass = Class.forName("sun.management.VMManagement");

Object vmManagement;

try {
vmManagement =
managementFactoryHelperClass.getDeclaredMethod("getVMManagement").invoke(null);
} catch (final NoSuchMethodException e) {
// Older vm before getVMManagement() existed
final Field field = managementFactoryHelperClass.getDeclaredField("jvm");
field.setAccessible(true);
vmManagement = field.get(null);
field.setAccessible(false);
}

//noinspection unchecked
return (List<String>) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
} catch (final ReflectiveOperationException | UnsatisfiedLinkError ignored) {
// Ignored exception
}

// Try IBM-based.
try {
final Class<?> VMClass = Class.forName("com.ibm.oti.vm.VM");
final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null);
return Arrays.asList(argArray);
} catch (final ReflectiveOperationException ignored) {
// Ignored exception
}

// Fallback to default
try {
System.err.println(
"WARNING: Unable to get VM args through reflection. A custom java.util.logging.LogManager may not work correctly");
return ManagementFactory.getRuntimeMXBean().getInputArguments();
} catch (final Throwable t) {
// Throws InvocationTargetException on modularized applications
// with non-opened java.management module
System.err.println("WARNING: Unable to get VM args using managed beans");
}
return Collections.emptyList();
}

private static void checkJarManifestMainClassIsThis(final URL jarUrl) throws IOException {
final URL manifestUrl = new URL("jar:" + jarUrl + "!/META-INF/MANIFEST.MF");
final String mainClassLine = "Main-Class: " + thisClass.getCanonicalName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class SpockRunner extends JUnitPlatform {
"datadog.slf4j",
"datadog.json",
"datadog.context",
"datadog.cli",
"datadog.appsec.api",
"datadog.trace.api",
"datadog.trace.bootstrap",
Expand Down
1 change: 1 addition & 0 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class CachedData {
exclude(project(':communication'))
exclude(project(':components:context'))
exclude(project(':components:json'))
exclude(project(':components:cli'))
exclude(project(':remote-config:remote-config-api'))
exclude(project(':remote-config:remote-config-core'))
exclude(project(':telemetry'))
Expand Down
1 change: 1 addition & 0 deletions internal-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ dependencies {
api project(':dd-trace-api')
api libs.slf4j
api project(':components:context')
api project(':components:cli')
api project(":utils:time-utils")

// has to be loaded by system classloader:
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ include ':dd-java-agent:agent-otel:otel-shim'
include ':dd-java-agent:agent-otel:otel-tooling'

include ':communication'
include ':components:cli'
include ':components:context'
include ':components:json'
include ':telemetry'
Expand Down
Loading