Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jbachorik committed Dec 8, 2023
1 parent 4d0b113 commit 50560b6
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public static void start(final Instrumentation inst, final URL agentJarURL, Stri
// multiple times
// If early profiling is enabled then this call will start profiling.
// If early profiling is disabled then later call will do this.
startProfilingAgent(true);
startProfilingAgent(true, inst);
} else {
log.debug("Oracle JDK 8 detected. Delaying profiler initialization.");
// Profiling can not run early on Oracle JDK 8 because it will cause JFR initialization
Expand All @@ -230,7 +230,7 @@ public static void start(final Instrumentation inst, final URL agentJarURL, Stri
new Runnable() {
@Override
public void run() {
startProfilingAgent(false);
startProfilingAgent(false, inst);
}
};
}
Expand Down Expand Up @@ -309,9 +309,9 @@ public void run() {

if (delayOkHttp) {
log.debug("Custom logger detected. Delaying Profiling initialization.");
registerLogManagerCallback(new StartProfilingAgentCallback());
registerLogManagerCallback(new StartProfilingAgentCallback(inst));
} else {
startProfilingAgent(false);
startProfilingAgent(false, inst);
// only enable instrumentation based profilers when we know JFR is ready
InstrumentationBasedProfiling.enableInstrumentationBasedProfiling();
}
Expand Down Expand Up @@ -480,14 +480,20 @@ public void execute() {
}

protected static class StartProfilingAgentCallback extends ClassLoadCallBack {
private final Instrumentation inst;

protected StartProfilingAgentCallback(Instrumentation inst) {
this.inst = inst;
}

@Override
public AgentThread agentThread() {
return PROFILER_STARTUP;
}

@Override
public void execute() {
startProfilingAgent(false);
startProfilingAgent(false, inst);
// only enable instrumentation based profilers when we know JFR is ready
InstrumentationBasedProfiling.enableInstrumentationBasedProfiling();
}
Expand Down Expand Up @@ -881,7 +887,7 @@ private static ProfilingContextIntegration createProfilingContextIntegration() {
return ProfilingContextIntegration.NoOp.INSTANCE;
}

private static void startProfilingAgent(final boolean isStartingFirst) {
private static void startProfilingAgent(final boolean isStartingFirst, Instrumentation inst) {
StaticEventLogger.begin("ProfilingAgent");

if (isAwsLambdaRuntime()) {
Expand All @@ -895,8 +901,9 @@ private static void startProfilingAgent(final boolean isStartingFirst) {
final Class<?> profilingAgentClass =
AGENT_CLASSLOADER.loadClass("com.datadog.profiling.agent.ProfilingAgent");
final Method profilingInstallerMethod =
profilingAgentClass.getMethod("run", Boolean.TYPE, ClassLoader.class);
profilingInstallerMethod.invoke(null, isStartingFirst, AGENT_CLASSLOADER);
profilingAgentClass.getMethod(
"run", Boolean.TYPE, ClassLoader.class, Instrumentation.class);
profilingInstallerMethod.invoke(null, isStartingFirst, AGENT_CLASSLOADER, inst);
/*
* Install the tracer hooks only when not using 'early start'.
* The 'early start' is happening so early that most of the infrastructure has not been set up yet.
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/agent-jmxfetch/integrations-core
2 changes: 2 additions & 0 deletions dd-java-agent/agent-profiling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ dependencies {
api project(':dd-java-agent:agent-profiling:profiling-auxiliary-ddprof')
api project(':dd-java-agent:agent-profiling:profiling-uploader')
api project(':dd-java-agent:agent-profiling:profiling-controller')
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr')
api project(':dd-java-agent:agent-profiling:profiling-controller-jfr:implementation')
api project(':dd-java-agent:agent-profiling:profiling-controller-ddprof')
api project(':dd-java-agent:agent-profiling:profiling-controller-openjdk')
api project(':dd-java-agent:agent-profiling:profiling-controller-oracle')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Set properties before any plugins get loaded
ext {
// the tests need Java 11 because the JFR writer got compiled with a version
// of ByteBuffer.position(int) which is binary incompatible with Java 8 ¯\_(ツ)_/¯
minJavaVersionForTests = JavaVersion.VERSION_11

// need access to jdk.jfr package
skipSettingCompilerRelease = true

excludeJdk = ['SEMERU11', 'SEMERU17']
}

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
}

excludedClassesCoverage += ['com.datadog.profiling.controller.jfr.JdkTypeIDs']


// Shared JFR implementation. The earliest Java version JFR is working on is Java 8

//tasks.named("compileTestJava").configure {
// setJavaVersion(it, 11)
// // tests should be compiled in Java 8 compatible way
// sourceCompatibility = JavaVersion.VERSION_1_8
// targetCompatibility = JavaVersion.VERSION_1_8
// // Disable '-processing' because some annotations are not claimed.
// // Disable '-options' because we are compiling for java8 without specifying bootstrap - intentionally.
// // Disable '-path' because we do not have some of the paths seem to be missing.
// options.compilerArgs.addAll(['-Xlint:all,-processing,-options,-path'/*, '-Werror'*/])
//}

forbiddenApisMain {
failOnMissingClasses = false
}

idea {
module {
jdkName = '11'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 void setStackDepth(int depth) {
JVM.getJVM().setStackDepth(depth);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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 {
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 {
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());

Class<?> jvmClass = Class.forName("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;
}

@Override
public void setStackDepth(int depth) {
try {
setStackDepthMH.invokeExact(depth);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.datadog.profiling.controller.jfr;

import java.lang.instrument.Instrumentation;
import java.util.ServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public static final JFRAccess NOOP =
new JFRAccess() {
@Override
public void setStackDepth(int depth) {}
};

public interface Factory {
JFRAccess create(Instrumentation inst);
}

private static volatile JFRAccess INSTANCE = NOOP;

public static JFRAccess instance() {
return INSTANCE;
}

public static void setup(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;
}

public abstract void setStackDepth(int depth);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.datadog.profiling.controller.ConfigurationException;
import com.datadog.profiling.controller.Controller;
import com.datadog.profiling.controller.UnsupportedEnvironmentException;
import com.datadog.profiling.controller.jfr.JFRAccess;
import com.datadog.profiling.controller.jfr.JfpUtils;
import com.datadog.profiling.controller.openjdk.events.AvailableProcessorCoresEvent;
import datadog.trace.api.Platform;
Expand Down Expand Up @@ -76,6 +77,8 @@ public static Controller instance(ConfigProvider configProvider) {
@SuppressForbidden
public OpenJdkController(final ConfigProvider configProvider)
throws ConfigurationException, ClassNotFoundException {
// configure the JFR stackdepth before we try to load any JFR classes
JFRAccess.instance().setStackDepth(getConfiguredStackDepth(configProvider));
// Make sure we can load JFR classes before declaring that we have successfully created
// factory and can use it.
Class.forName("jdk.jfr.Recording");
Expand Down Expand Up @@ -246,4 +249,9 @@ private static void enableEvent(
private static boolean isEventEnabled(Map<String, String> recordingSettings, String event) {
return Boolean.parseBoolean(recordingSettings.get(event + "#enabled"));
}

private int getConfiguredStackDepth(ConfigProvider configProvider) {
return configProvider.getInteger(
ProfilingConfig.PROFILING_STACKDEPTH, ProfilingConfig.PROFILING_STACKDEPTH_DEFAULT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_SCHEDULING_EVENT_INTERVAL;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_STACKDEPTH;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_STACKDEPTH_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_COLLAPSING;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_COLLAPSING_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_CONTEXT_FILTER;
Expand All @@ -45,6 +44,8 @@
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_INTERVAL_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_STACKDEPTH;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_STACKDEPTH_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ULTRA_MINIMAL;
import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_ENABLED;

Expand Down Expand Up @@ -217,8 +218,9 @@ public static int getMemleakCapacity() {
public static int getStackDepth(ConfigProvider configProvider) {
return getInteger(
configProvider,
PROFILING_DATADOG_PROFILER_STACKDEPTH,
PROFILING_DATADOG_PROFILER_STACKDEPTH_DEFAULT);
PROFILING_STACKDEPTH,
PROFILING_STACKDEPTH_DEFAULT,
PROFILING_DATADOG_PROFILER_STACKDEPTH);
}

public static int getStackDepth() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.datadog.profiling.controller.Controller;
import com.datadog.profiling.controller.ProfilingSystem;
import com.datadog.profiling.controller.UnsupportedEnvironmentException;
import com.datadog.profiling.controller.jfr.JFRAccess;
import com.datadog.profiling.uploader.ProfileUploader;
import datadog.trace.api.Config;
import datadog.trace.api.Platform;
Expand All @@ -17,6 +18,7 @@
import datadog.trace.api.profiling.RecordingType;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -80,7 +82,8 @@ public void onNewData(RecordingType type, RecordingData data, boolean handleSync
* Main entry point into profiling Note: this must be reentrant because we may want to start
* profiling before any other tool, and then attempt to start it again at normal time
*/
public static synchronized void run(final boolean isStartingFirst, ClassLoader agentClasLoader)
public static synchronized void run(
final boolean isStartingFirst, ClassLoader agentClasLoader, Instrumentation inst)
throws IllegalArgumentException, IOException {
if (profiler == null) {
final Config config = Config.get();
Expand Down Expand Up @@ -112,6 +115,7 @@ public static synchronized void run(final boolean isStartingFirst, ClassLoader a
}

try {
JFRAccess.setup(inst);
final Controller controller = ControllerFactory.createController(configProvider);

String dumpPath = configProvider.getString(ProfilingConfig.PROFILING_DEBUG_DUMP_PATH);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class ProfilerInstaller {
public static void installProfiler() {
if (Config.get().isProfilingEnabled()) {
try {
ProfilingAgent.run(true, null);
ProfilingAgent.run(true, null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Loading

0 comments on commit 50560b6

Please sign in to comment.