Skip to content

Commit bfe5a1f

Browse files
committed
Expose & use jvmstat for JDK 9+ programmatically
Squashed 12 commits
1 parent 3e2867a commit bfe5a1f

File tree

8 files changed

+90
-0
lines changed

8 files changed

+90
-0
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import datadog.trace.util.AgentTaskScheduler;
4343
import datadog.trace.util.AgentThreadFactory.AgentThread;
4444
import datadog.trace.util.throwable.FatalAgentMisconfigurationError;
45+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4546
import java.lang.instrument.Instrumentation;
4647
import java.lang.reflect.InvocationTargetException;
4748
import java.lang.reflect.Method;
@@ -278,6 +279,8 @@ public static void start(
278279
codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN);
279280
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);
280281

282+
patchJPSAccess(inst);
283+
281284
if (profilingEnabled) {
282285
if (!isOracleJDK8()) {
283286
// Profiling agent startup code is written in a way to allow `startProfilingAgent` be called
@@ -407,6 +410,20 @@ private static void injectAgentArgsConfig(String agentArgs) {
407410
}
408411
}
409412

413+
@SuppressForbidden
414+
public static void patchJPSAccess(Instrumentation inst) {
415+
if (Platform.isJavaVersionAtLeast(9)) {
416+
// Unclear if supported for J9, may need to revisit
417+
try {
418+
Class.forName("datadog.trace.util.JPMSJPSAccess")
419+
.getMethod("patchModuleAccess")
420+
.invoke(inst);
421+
} catch (Exception e) {
422+
log.warn("Failed to patch module access for jvmstat");
423+
}
424+
}
425+
}
426+
410427
public static void shutdown(final boolean sync) {
411428
StaticEventLogger.end("Agent");
412429
StaticEventLogger.stop();

dd-java-agent/agent-profiling/profiling-utils/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Set properties before any plugins get loaded
22
ext {
3+
// In order to patch access for jvmstat for JDK >8
4+
minJavaVersionForTests = JavaVersion.VERSION_11
35
}
46

57
apply from: "$rootDir/gradle/java.gradle"

dd-smoke-tests/profiling-integration-tests/src/test/java/datadog/smoketest/JFRBasedProfilingIntegrationTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ void testShutdown(final TestInfo testInfo) throws Exception {
507507
assertTrue(
508508
targetProcess.waitFor(
509509
duration + PROFILING_UPLOAD_TIMEOUT_SECONDS + 1, TimeUnit.SECONDS));
510+
assertTrue(
511+
checkLogLines(logFilePath, it -> it.contains("Successfully invoked jvmstat")));
510512
} finally {
511513
if (targetProcess != null) {
512514
targetProcess.destroyForcibly();

internal-api/build.gradle

Lines changed: 1 addition & 0 deletions
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",

internal-api/internal-api-9/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ forbiddenApisMain {
4141
failOnMissingClasses = false
4242
}
4343

44+
forbiddenApisMain_java11 {
45+
failOnMissingClasses = false
46+
}
47+
4448
idea {
4549
module {
4650
jdkName = '11'
Lines changed: 26 additions & 0 deletions
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 = JPMSJPSAccess.class.getClassLoader().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+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package datadog.trace.util;
2+
3+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4+
import java.lang.reflect.Method;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Set;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
public final class JPSUtils {
12+
private static final Logger log = LoggerFactory.getLogger(JPSUtils.class);
13+
14+
@SuppressForbidden
15+
public static Set<String> getVMPids() {
16+
Set<String> vmPids = new HashSet<>();
17+
try {
18+
Class<?> monitoredHostClass = Class.forName("sun.jvmstat.monitor.MonitoredHost");
19+
Method getMonitoredHostMethod =
20+
monitoredHostClass.getDeclaredMethod("getMonitoredHost", String.class);
21+
Object vmHost = getMonitoredHostMethod.invoke(null, "localhost");
22+
for (Integer vmPid :
23+
(List<Integer>) monitoredHostClass.getDeclaredMethod("activeVms").invoke(vmHost)) {
24+
vmPids.add(vmPid.toString());
25+
}
26+
log.debug("Successfully invoked jvmstat");
27+
} catch (Exception e) {
28+
log.debug("Failed to invoke jvmstat with exception ", e);
29+
return null;
30+
}
31+
return vmPids;
32+
}
33+
}

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

Lines changed: 5 additions & 0 deletions
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)