Skip to content
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

Use jvmstat for JDKs 9+ programmatically #8641

Merged
merged 10 commits into from
Apr 10, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static datadog.trace.api.ConfigDefaults.DEFAULT_STARTUP_LOGS_ENABLED;
import static datadog.trace.api.Platform.isJavaVersionAtLeast;
import static datadog.trace.api.Platform.isOracleJDK8;
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
import static datadog.trace.bootstrap.Library.WILDFLY;
import static datadog.trace.bootstrap.Library.detectLibraries;
import static datadog.trace.util.AgentThreadFactory.AgentThread.JMX_STARTUP;
Expand Down Expand Up @@ -44,6 +45,7 @@
import datadog.trace.util.AgentTaskScheduler;
import datadog.trace.util.AgentThreadFactory.AgentThread;
import datadog.trace.util.throwable.FatalAgentMisconfigurationError;
import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -291,6 +293,8 @@ public static void start(
codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN);
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);

patchJPSAccess(inst);

if (profilingEnabled) {
if (!isOracleJDK8()) {
// Profiling agent startup code is written in a way to allow `startProfilingAgent` be called
Expand Down Expand Up @@ -420,6 +424,23 @@ private static void injectAgentArgsConfig(String agentArgs) {
}
}

@SuppressForbidden
public static void patchJPSAccess(Instrumentation inst) {
if (Platform.isJavaVersionAtLeast(9)) {
// Unclear if supported for J9, may need to revisit
try {
Class.forName("datadog.trace.util.JPMSJPSAccess")
.getMethod("patchModuleAccess", Instrumentation.class)
.invoke(null, inst);
} catch (Exception e) {
log.debug(
SEND_TELEMETRY,
"Failed to patch module access for jvmstat and Java version "
+ Platform.getRuntimeVersion());
}
}
}

public static void shutdown(final boolean sync) {
StaticEventLogger.end("Agent");
StaticEventLogger.stop();
Expand Down
1 change: 1 addition & 0 deletions internal-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ excludedClassesCoverage += [
"datadog.trace.util.ComparableVersion.LongItem",
"datadog.trace.util.ComparableVersion.StringItem",
"datadog.trace.util.ConcurrentEnumMap",
"datadog.trace.util.JPSUtils",
"datadog.trace.util.MethodHandles",
"datadog.trace.util.PidHelper",
"datadog.trace.util.PidHelper.Fallback",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package datadog.trace.util;

import java.lang.instrument.Instrumentation;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

public class JPMSJPSAccess {
public static void patchModuleAccess(Instrumentation inst) {
Module unnamedModule = ClassLoader.getSystemClassLoader().getUnnamedModule();
Module jvmstatModule = ModuleLayer.boot().findModule("jdk.internal.jvmstat").orElse(null);

if (jvmstatModule != null) {
Map<String, Set<Module>> extraOpens = Map.of("sun.jvmstat.monitor", Set.of(unnamedModule));

// Redefine the module
inst.redefineModule(
jvmstatModule,
Collections.emptySet(),
extraOpens,
extraOpens,
Collections.emptySet(),
Collections.emptyMap());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package datadog.trace.util

import datadog.trace.test.util.DDSpecification
import net.bytebuddy.agent.ByteBuddyAgent

class PidHelperTest extends DDSpecification {

def "PID is available everywhere we test"() {
expect:
!PidHelper.getPid().isEmpty()
}

def "JPS via jvmstat is used when possible"() {
when:
def inst = ByteBuddyAgent.install()
JPMSJPSAccess.patchModuleAccess(inst)

then:
JPSUtils.VMPids != null
}
}
25 changes: 25 additions & 0 deletions internal-api/src/main/java/datadog/trace/util/JPSUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package datadog.trace.util;

import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.lang.reflect.Method;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class JPSUtils {
private static final Logger log = LoggerFactory.getLogger(JPSUtils.class);

@SuppressForbidden
public static Set<String> getVMPids() {
try {
Class<?> monitoredHostClass = Class.forName("sun.jvmstat.monitor.MonitoredHost");
Method getMonitoredHostMethod =
monitoredHostClass.getDeclaredMethod("getMonitoredHost", String.class);
Object vmHost = getMonitoredHostMethod.invoke(null, "localhost");
return (Set<String>) monitoredHostClass.getDeclaredMethod("activeVms").invoke(vmHost);
} catch (Exception e) {
log.debug("Failed to invoke jvmstat with exception ", e);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ private static String findPid() {
}

public static Set<String> getJavaPids() {
// Attempt to use jvmstat directly, fall through to jps process fork strategy
Set<String> directlyObtainedPids = JPSUtils.getVMPids();
if (directlyObtainedPids != null) {
return directlyObtainedPids;
}
// there is no supported Java API to achieve this
// one could use sun.jvmstat.monitor.MonitoredHost but it is an internal API and can go away at
// any time -
Expand Down