-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow setting JFR stackdepth value from agent (#6335)
- Loading branch information
Showing
19 changed files
with
393 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
dd-java-agent/agent-profiling/profiling-controller-jfr/implementation/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Set properties before any plugins get loaded | ||
ext { | ||
minJavaVersionForTests = JavaVersion.VERSION_1_8 | ||
|
||
// need access to jdk.jfr package | ||
skipSettingCompilerRelease = true | ||
} | ||
|
||
apply from: "$rootDir/gradle/java.gradle" | ||
apply plugin: 'idea' | ||
|
||
|
||
sourceSets { | ||
"main_java11" { | ||
java.srcDirs "${project.projectDir}/src/main/java11" | ||
} | ||
} | ||
|
||
compileMain_java11Java.configure { | ||
setJavaVersion(it, 11) | ||
sourceCompatibility = JavaVersion.VERSION_1_9 | ||
targetCompatibility = JavaVersion.VERSION_1_9 | ||
} | ||
|
||
dependencies { | ||
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr') | ||
main_java11CompileOnly project(':dd-java-agent:agent-profiling:profiling-controller-jfr') | ||
|
||
implementation deps.slf4j | ||
|
||
testImplementation deps.mockito | ||
testImplementation deps.junit5 | ||
testImplementation sourceSets.main_java11.output | ||
} | ||
|
||
excludedClassesCoverage += [ | ||
'com.datadog.profiling.controller.jfr.JPMSJFRAccess*', | ||
// The tests will be run only on Java 8 so we exclude the coverage check to prevent failures for other Java versions | ||
'com.datadog.profiling.controller.jfr.SimpleJFRAccess*' | ||
] | ||
|
||
jar { | ||
from sourceSets.main_java11.output | ||
} | ||
|
||
forbiddenApisMain { | ||
failOnMissingClasses = false | ||
} | ||
|
||
idea { | ||
module { | ||
jdkName = '11' | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...fr/implementation/src/main/java/com/datadog/profiling/controller/jfr/SimpleJFRAccess.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.datadog.profiling.controller.jfr; | ||
|
||
import datadog.trace.api.Platform; | ||
import java.lang.instrument.Instrumentation; | ||
import jdk.jfr.internal.JVM; | ||
|
||
public class SimpleJFRAccess extends JFRAccess { | ||
public static class FactoryImpl implements JFRAccess.Factory { | ||
@Override | ||
public JFRAccess create(Instrumentation inst) { | ||
return !Platform.isJ9() && Platform.isJavaVersion(8) ? new SimpleJFRAccess() : null; | ||
} | ||
} | ||
|
||
@Override | ||
public boolean setStackDepth(int depth) { | ||
JVM.getJVM().setStackDepth(depth); | ||
return true; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
...fr/implementation/src/main/java11/com/datadog/profiling/controller/jfr/JPMSJFRAccess.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.datadog.profiling.controller.jfr; | ||
|
||
import datadog.trace.api.Platform; | ||
import java.lang.instrument.Instrumentation; | ||
import java.lang.invoke.MethodHandle; | ||
import java.lang.invoke.MethodHandles; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import jdk.jfr.Event; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class JPMSJFRAccess extends JFRAccess { | ||
private static final Logger log = LoggerFactory.getLogger(JPMSJFRAccess.class); | ||
|
||
public static final class FactoryImpl implements JFRAccess.Factory { | ||
private static final Logger log = LoggerFactory.getLogger(FactoryImpl.class); | ||
|
||
@Override | ||
public JFRAccess create(Instrumentation inst) { | ||
if (!Platform.isJ9() && Platform.isJavaVersionAtLeast(9)) { | ||
try { | ||
return new JPMSJFRAccess(inst); | ||
} catch (Exception e) { | ||
log.debug("Unable to obtain JFR internal access", e); | ||
} | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
private final MethodHandle setStackDepthMH; | ||
|
||
public JPMSJFRAccess(Instrumentation inst) throws Exception { | ||
patchModuleAccess(inst); | ||
|
||
Class<?> jvmClass = JFRAccess.class.getClassLoader().loadClass("jdk.jfr.internal.JVM"); | ||
Method m = jvmClass.getMethod("setStackDepth", int.class); | ||
m.setAccessible(true); | ||
MethodHandle mh = MethodHandles.publicLookup().unreflect(m); | ||
if (!Modifier.isStatic(m.getModifiers())) { | ||
// instance method - need to call JVM.getJVM() and bind the instance | ||
Object jvm = jvmClass.getMethod("getJVM").invoke(null); | ||
mh = mh.bindTo(jvm); | ||
} | ||
setStackDepthMH = mh; | ||
} | ||
|
||
private static void patchModuleAccess(Instrumentation inst) { | ||
Module unnamedModule = JFRAccess.class.getClassLoader().getUnnamedModule(); | ||
Module targetModule = Event.class.getModule(); | ||
|
||
Map<String, Set<Module>> extraOpens = Map.of("jdk.jfr.internal", Set.of(unnamedModule)); | ||
|
||
// Redefine the module | ||
inst.redefineModule( | ||
targetModule, | ||
Collections.emptySet(), | ||
extraOpens, | ||
extraOpens, | ||
Collections.emptySet(), | ||
Collections.emptyMap()); | ||
} | ||
|
||
@Override | ||
public boolean setStackDepth(int depth) { | ||
try { | ||
setStackDepthMH.invokeExact(depth); | ||
return true; | ||
} catch (Throwable throwable) { | ||
log.warn("Unable to set JFR stack depth", throwable); | ||
} | ||
return false; | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
...c/main/resources/META-INF/services/com.datadog.profiling.controller.jfr.JFRAccess$Factory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
com.datadog.profiling.controller.jfr.SimpleJFRAccess$FactoryImpl | ||
com.datadog.profiling.controller.jfr.JPMSJFRAccess$FactoryImpl |
33 changes: 33 additions & 0 deletions
33
...-jfr/implementation/src/test/java/com/datadog/profiling/controller/jfr/JFRAccessTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.datadog.profiling.controller.jfr; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
import static org.junit.jupiter.api.Assumptions.assumeTrue; | ||
|
||
import datadog.trace.api.Platform; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class JFRAccessTest { | ||
@Test | ||
void testJava8JFRAccess() { | ||
// For Java 9 and above, the JFR access requires instrumentation in order to patch the module | ||
// access | ||
assumeTrue(Platform.isJavaVersion(8) && !Platform.isJ9()); | ||
|
||
// just do a sanity check that it is possible to instantiate the class and call | ||
// 'setStackDepth()' | ||
SimpleJFRAccess jfrAccess = new SimpleJFRAccess(); | ||
assertTrue(jfrAccess.setStackDepth(42)); | ||
} | ||
|
||
@Test | ||
void testJ9JFRAccess() { | ||
assumeTrue(Platform.isJ9()); | ||
|
||
// need to run a bogus setup first | ||
JFRAccess.setup(null); | ||
// make sure that an attempt to get the instance returns the NOOP implementation and does not | ||
// throw exceptions | ||
assertEquals(JFRAccess.NOOP, JFRAccess.instance()); | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
...rofiling-controller-jfr/src/main/java/com/datadog/profiling/controller/jfr/JFRAccess.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package com.datadog.profiling.controller.jfr; | ||
|
||
import java.lang.instrument.Instrumentation; | ||
import java.util.ServiceLoader; | ||
import javax.annotation.Nullable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* JFR access support.<br> | ||
* Provides access to the JFR internal API. For Java 9 and newer, the JFR access requires | ||
* instrumentation in order to patch the module access. | ||
*/ | ||
public abstract class JFRAccess { | ||
private static final Logger log = LoggerFactory.getLogger(JFRAccess.class); | ||
|
||
/** No-op JFR access implementation. */ | ||
public static final JFRAccess NOOP = | ||
new JFRAccess() { | ||
@Override | ||
public boolean setStackDepth(int depth) { | ||
return false; | ||
} | ||
}; | ||
|
||
/** | ||
* Factory for JFR access.<br> | ||
* The factory is expected to return {@code null} if the JFR access is not available. The factory | ||
* is to be registered in {@code | ||
* META-INF/services/com.datadog.profiling.controller.jfr.JFRAccess$Factory} and will be | ||
* discovered using {@link ServiceLoader}. | ||
*/ | ||
public interface Factory { | ||
@Nullable | ||
JFRAccess create(@Nullable Instrumentation inst); | ||
} | ||
|
||
private static volatile JFRAccess INSTANCE = NOOP; | ||
|
||
/** | ||
* Returns the JFR access instance. | ||
* | ||
* @return the JFR access instance or {@link #NOOP} if the JFR access is not available | ||
*/ | ||
public static JFRAccess instance() { | ||
return INSTANCE; | ||
} | ||
|
||
/** | ||
* Sets up the JFR access.<br> | ||
* The method is expected to be called once, before any other method of this class is called. | ||
* | ||
* @param inst the instrumentation instance, may be {@code null} | ||
*/ | ||
public static void setup(@Nullable Instrumentation inst) { | ||
JFRAccess access = NOOP; | ||
if (inst != null) { | ||
for (Factory factory : ServiceLoader.load(Factory.class, JFRAccess.class.getClassLoader())) { | ||
JFRAccess candidate = factory.create(inst); | ||
if (candidate != null) { | ||
access = candidate; | ||
break; | ||
} | ||
} | ||
} | ||
log.debug("JFR access: {}", access.getClass().getName()); | ||
INSTANCE = access; | ||
} | ||
|
||
/** | ||
* Sets the stack depth for the JFR recordings.<br> | ||
* It needs to be called before the recording is started. | ||
* | ||
* @param depth the stack depth | ||
* @return {@code true} if the stack depth was set successfully, {@code false} if not or it is not | ||
* possible to tell | ||
*/ | ||
public abstract boolean setStackDepth(int depth); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.