Skip to content

Commit fe81e49

Browse files
authored
Use jvmstat for JDKs 9+ programmatically (#8641)
1 parent c65d843 commit fe81e49

File tree

6 files changed

+88
-0
lines changed

6 files changed

+88
-0
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static datadog.trace.api.ConfigDefaults.DEFAULT_STARTUP_LOGS_ENABLED;
44
import static datadog.trace.api.Platform.isJavaVersionAtLeast;
55
import static datadog.trace.api.Platform.isOracleJDK8;
6+
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
67
import static datadog.trace.bootstrap.Library.WILDFLY;
78
import static datadog.trace.bootstrap.Library.detectLibraries;
89
import static datadog.trace.util.AgentThreadFactory.AgentThread.JMX_STARTUP;
@@ -44,6 +45,7 @@
4445
import datadog.trace.util.AgentTaskScheduler;
4546
import datadog.trace.util.AgentThreadFactory.AgentThread;
4647
import datadog.trace.util.throwable.FatalAgentMisconfigurationError;
48+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4749
import java.lang.instrument.Instrumentation;
4850
import java.lang.reflect.InvocationTargetException;
4951
import java.lang.reflect.Method;
@@ -291,6 +293,8 @@ public static void start(
291293
codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN);
292294
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);
293295

296+
patchJPSAccess(inst);
297+
294298
if (profilingEnabled) {
295299
if (!isOracleJDK8()) {
296300
// Profiling agent startup code is written in a way to allow `startProfilingAgent` be called
@@ -420,6 +424,23 @@ private static void injectAgentArgsConfig(String agentArgs) {
420424
}
421425
}
422426

427+
@SuppressForbidden
428+
public static void patchJPSAccess(Instrumentation inst) {
429+
if (Platform.isJavaVersionAtLeast(9)) {
430+
// Unclear if supported for J9, may need to revisit
431+
try {
432+
Class.forName("datadog.trace.util.JPMSJPSAccess")
433+
.getMethod("patchModuleAccess", Instrumentation.class)
434+
.invoke(null, inst);
435+
} catch (Exception e) {
436+
log.debug(
437+
SEND_TELEMETRY,
438+
"Failed to patch module access for jvmstat and Java version "
439+
+ Platform.getRuntimeVersion());
440+
}
441+
}
442+
}
443+
423444
public static void shutdown(final boolean sync) {
424445
StaticEventLogger.end("Agent");
425446
StaticEventLogger.stop();

internal-api/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ excludedClassesCoverage += [
175175
"datadog.trace.util.ComparableVersion.LongItem",
176176
"datadog.trace.util.ComparableVersion.StringItem",
177177
"datadog.trace.util.ConcurrentEnumMap",
178+
"datadog.trace.util.JPSUtils",
178179
"datadog.trace.util.MethodHandles",
179180
"datadog.trace.util.PidHelper",
180181
"datadog.trace.util.PidHelper.Fallback",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package datadog.trace.util;
2+
3+
import java.lang.instrument.Instrumentation;
4+
import java.util.Collections;
5+
import java.util.Map;
6+
import java.util.Set;
7+
8+
public class JPMSJPSAccess {
9+
public static void patchModuleAccess(Instrumentation inst) {
10+
Module unnamedModule = ClassLoader.getSystemClassLoader().getUnnamedModule();
11+
Module jvmstatModule = ModuleLayer.boot().findModule("jdk.internal.jvmstat").orElse(null);
12+
13+
if (jvmstatModule != null) {
14+
Map<String, Set<Module>> extraOpens = Map.of("sun.jvmstat.monitor", Set.of(unnamedModule));
15+
16+
// Redefine the module
17+
inst.redefineModule(
18+
jvmstatModule,
19+
Collections.emptySet(),
20+
extraOpens,
21+
extraOpens,
22+
Collections.emptySet(),
23+
Collections.emptyMap());
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package datadog.trace.util
22

33
import datadog.trace.test.util.DDSpecification
4+
import net.bytebuddy.agent.ByteBuddyAgent
45

56
class PidHelperTest extends DDSpecification {
67

78
def "PID is available everywhere we test"() {
89
expect:
910
!PidHelper.getPid().isEmpty()
1011
}
12+
13+
def "JPS via jvmstat is used when possible"() {
14+
when:
15+
def inst = ByteBuddyAgent.install()
16+
JPMSJPSAccess.patchModuleAccess(inst)
17+
18+
then:
19+
JPSUtils.VMPids != null
20+
}
1121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package datadog.trace.util;
2+
3+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4+
import java.lang.reflect.Method;
5+
import java.util.Set;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
public final class JPSUtils {
10+
private static final Logger log = LoggerFactory.getLogger(JPSUtils.class);
11+
12+
@SuppressForbidden
13+
public static Set<String> getVMPids() {
14+
try {
15+
Class<?> monitoredHostClass = Class.forName("sun.jvmstat.monitor.MonitoredHost");
16+
Method getMonitoredHostMethod =
17+
monitoredHostClass.getDeclaredMethod("getMonitoredHost", String.class);
18+
Object vmHost = getMonitoredHostMethod.invoke(null, "localhost");
19+
return (Set<String>) monitoredHostClass.getDeclaredMethod("activeVms").invoke(vmHost);
20+
} catch (Exception e) {
21+
log.debug("Failed to invoke jvmstat with exception ", e);
22+
return null;
23+
}
24+
}
25+
}

internal-api/src/main/java/datadog/trace/util/PidHelper.java

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ private static String findPid() {
6565
}
6666

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

0 commit comments

Comments
 (0)