Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -325,19 +324,20 @@ public static void start(
}
}

boolean retryProfilerStart = false;
if (profilingEnabled) {
if (!isOracleJDK8()) {
// Profiling agent startup code is written in a way to allow `startProfilingAgent` be called
// 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, inst);
retryProfilerStart = startProfilingAgent(true, 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
// deadlock.
// Oracle JDK 8 JFR controller requires JMX so register an 'after-jmx-initialized' callback.
PROFILER_INIT_AFTER_JMX = () -> startProfilingAgent(false, inst);
PROFILER_INIT_AFTER_JMX = () -> startProfilingAgent(false, true, inst);
}
}

Expand Down Expand Up @@ -433,7 +433,7 @@ public static void start(
log.debug("Custom logger detected. Delaying Profiling initialization.");
registerLogManagerCallback(new StartProfilingAgentCallback(inst));
} else {
startProfilingAgent(false, inst);
startProfilingAgent(false, retryProfilerStart, inst);
// only enable instrumentation based profilers when we know JFR is ready
InstrumentationBasedProfiling.enableInstrumentationBasedProfiling();
}
Expand Down Expand Up @@ -652,7 +652,7 @@ public AgentThread agentThread() {

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

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

private static boolean startProfilingAgent(
final boolean earlyStart, final boolean firstAttempt, Instrumentation inst) {
if (isAwsLambdaRuntime()) {
log.info("Profiling not supported in AWS Lambda runtimes");
return;
if (firstAttempt) {
log.info("Profiling not supported in AWS Lambda runtimes");
}
return false;
}

final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AGENT_CLASSLOADER);
final Class<?> profilingAgentClass =
AGENT_CLASSLOADER.loadClass("com.datadog.profiling.agent.ProfilingAgent");
final Method profilingInstallerMethod =
profilingAgentClass.getMethod(
"run", Boolean.TYPE, ClassLoader.class, Instrumentation.class);
profilingInstallerMethod.invoke(null, isStartingFirst, AGENT_CLASSLOADER, inst);
boolean requestRetry = false;

if (firstAttempt) {
StaticEventLogger.begin("ProfilingAgent");
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AGENT_CLASSLOADER);
final Class<?> profilingAgentClass =
AGENT_CLASSLOADER.loadClass("com.datadog.profiling.agent.ProfilingAgent");
final Method profilingInstallerMethod =
profilingAgentClass.getMethod("run", Boolean.TYPE, Instrumentation.class);
requestRetry = (boolean) profilingInstallerMethod.invoke(null, earlyStart, inst);
} catch (final Throwable ex) {
log.error(SEND_TELEMETRY, "Throwable thrown while starting profiling agent", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
StaticEventLogger.end("ProfilingAgent");
}
if (!earlyStart) {
/*
* 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.
*/
if (!isStartingFirst) {
log.debug("Scheduling scope event factory registration");
WithGlobalTracer.registerOrExecute(
new WithGlobalTracer.Callback() {
@Override
public void withTracer(TracerAPI tracer) {
log.debug("Initializing profiler tracer integrations");
tracer.getProfilingContext().onStart();
}
});
}
} catch (final Throwable t) {
log.error(
SEND_TELEMETRY,
"Throwable thrown while starting profiling agent "
+ Arrays.toString(t.getCause().getStackTrace()));
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
initProfilerContext();
}
return requestRetry;
}

StaticEventLogger.end("ProfilingAgent");
private static void initProfilerContext() {
log.debug("Scheduling profiler context initialization");
WithGlobalTracer.registerOrExecute(
tracer -> {
log.debug("Initializing profiler context integration");
tracer.getProfilingContext().onStart();
});
}

private static boolean isAwsLambdaRuntime() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ excludedClassesCoverage += [
dependencies {
api libs.slf4j
api project(':internal-api')
api project(':utils:version-utils')
implementation project(path: ':dd-java-agent:agent-profiling:profiling-ddprof', configuration: 'shadow')
api project(':dd-java-agent:agent-profiling:profiling-ddprof')
api project(':dd-java-agent:agent-profiling:profiling-controller')
api project(':dd-java-agent:agent-profiling:profiling-utils')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.datadog.profiling.controller.ProfilerSettingsSupport;
import com.datadog.profiling.ddprof.DatadogProfiler;
import datadog.common.version.VersionInfo;
import datadog.trace.bootstrap.config.provider.ConfigProvider;

public class DatadogProfilerSettings extends ProfilerSettingsSupport {
Expand All @@ -14,6 +15,7 @@ public DatadogProfilerSettings(DatadogProfiler datadogProfiler) {
}

public void publish() {
datadogProfiler.recordSetting(VERSION_KEY, VersionInfo.VERSION);
datadogProfiler.recordSetting(UPLOAD_PERIOD_KEY, String.valueOf(uploadPeriod), "seconds");
datadogProfiler.recordSetting(UPLOAD_TIMEOUT_KEY, String.valueOf(uploadTimeout), "seconds");
datadogProfiler.recordSetting(UPLOAD_COMPRESSION_KEY, uploadCompression);
Expand All @@ -27,7 +29,9 @@ public void publish() {
datadogProfiler.recordSetting(PERF_EVENTS_PARANOID_KEY, perfEventsParanoid);
datadogProfiler.recordSetting(NATIVE_STACKS_KEY, String.valueOf(hasNativeStacks));
datadogProfiler.recordSetting(JFR_IMPLEMENTATION_KEY, "ddprof");
datadogProfiler.recordSetting(STACK_DEPTH_KEY, String.valueOf(stackDepth));
datadogProfiler.recordSetting(
"ddprof " + STACK_DEPTH_KEY,
String.valueOf(requestedStackDepth)); // ddprof-java will accept the requested stack depth
datadogProfiler.recordSetting(SELINUX_STATUS_KEY, seLinuxStatus);
if (serviceInstrumentationType != null) {
datadogProfiler.recordSetting(SERVICE_INSTRUMENTATION_TYPE, serviceInstrumentationType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,13 @@ idea {
jdkName = '11'
}
}

project.afterEvaluate {
tasks.withType(Test).configureEach {
if (javaLauncher.get().metadata.languageVersion.asInt() >= 9) {
jvmArgs += [
'--add-opens',
'jdk.jfr/jdk.jfr.internal=ALL-UNNAMED'] // JPMSJFRAccess needs access to jdk.jfr.internal package
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,30 @@ public JPMSJFRAccess(Instrumentation inst) throws Exception {

jvmClass = JFRAccess.class.getClassLoader().loadClass("jdk.jfr.internal.JVM");
repositoryClass = JFRAccess.class.getClassLoader().loadClass("jdk.jfr.internal.Repository");
safePathClass =
JFRAccess.class.getClassLoader().loadClass("jdk.jfr.internal.SecuritySupport$SafePath");
safePathClass = safePathClass();
Object jvm = getJvm();
setStackDepthMH = getJvmMethodHandle(jvm, "setStackDepth", int.class);
setRepositoryBaseMH = setRepositoryBaseMethodHandle();
counterTimeMH = getJvmMethodHandle(jvm, "counterTime");
getTimeConversionFactorMH = getJvmMethodHandle(jvm, "getTimeConversionFactor");
}

private Object getJvm()
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return jvmClass.getMethod("getJVM").invoke(null);
private static Class<?> safePathClass() {
try {
return JFRAccess.class
.getClassLoader()
.loadClass("jdk.jfr.internal.SecuritySupport$SafePath");
} catch (ClassNotFoundException e) {
return Path.class; // no SafePath with SecurityManager gone
}
}

private Object getJvm() {
try {
return jvmClass.getMethod("getJVM").invoke(null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
return null;
}

private MethodHandle getJvmMethodHandle(Object jvm, String method, Class... args)
Expand Down Expand Up @@ -97,6 +109,11 @@ private MethodHandle setRepositoryBaseMethodHandle()
}

private static void patchModuleAccess(Instrumentation inst) {
if (inst == null) {
// used in testing; we don't have instrumentation and will patch the module access in the test
// task
return;
}
Module unnamedModule = JFRAccess.class.getClassLoader().getUnnamedModule();
Module targetModule = Event.class.getModule();

Expand Down Expand Up @@ -126,7 +143,10 @@ public boolean setStackDepth(int depth) {
@Override
public boolean setBaseLocation(String location) {
try {
Object safePath = safePathClass.getConstructor(Path.class).newInstance(Paths.get(location));
Object safePath =
Path.class.isAssignableFrom(safePathClass)
? Paths.get(location)
: safePathClass.getConstructor(Path.class).newInstance(Paths.get(location));
setRepositoryBaseMH.invoke(safePath);
return true;
} catch (Throwable throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static datadog.environment.JavaVirtualMachine.isJ9;
import static datadog.environment.JavaVirtualMachine.isJavaVersion;
import static datadog.environment.JavaVirtualMachine.isJavaVersionAtLeast;
import static datadog.environment.JavaVirtualMachine.isOracleJDK8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -22,6 +23,18 @@ void testJava8JFRAccess() {
assertTrue(jfrAccess.setStackDepth(42));
}

@Test
void testJPMSJFRAccess() throws Exception {
// For Java 9 and above, the JFR access requires instrumentation in order to patch the module
// access
assumeTrue(isJavaVersionAtLeast(9) && !isJ9());

// just do a sanity check that it is possible to instantiate the class and call
// 'setStackDepth()'
JPMSJFRAccess jfrAccess = new JPMSJFRAccess(null);
assertTrue(jfrAccess.setStackDepth(42));
}

@Test
void testJ9JFRAccess() {
assumeTrue(isJ9());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ apply plugin: 'idea'
dependencies {
api libs.slf4j
api project(':internal-api')
api project(':utils:version-utils')
api(project(':dd-java-agent:agent-bootstrap')) {
exclude group: 'com.datadoghq', module: 'agent-logging'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.datadog.profiling.controller.openjdk;

import com.datadog.profiling.controller.ControllerContext;
import com.datadog.profiling.controller.ProfilerSettingsSupport;
import com.datadog.profiling.controller.openjdk.events.ProfilerSettingEvent;
import datadog.common.version.VersionInfo;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.api.Platform;
import datadog.trace.bootstrap.config.provider.ConfigProvider;
Expand All @@ -14,20 +16,23 @@ final class JfrProfilerSettings extends ProfilerSettingsSupport {
private static final String EXCEPTION_HISTO_REPORT_LIMIT_KEY = "Exception Histo Report Limit";
private static final String EXCEPTION_HISTO_SIZE_LIMIT_KEY = "Exception Histo Size Limit";
private final String jfrImplementation;
private final boolean isDdprofActive;

public JfrProfilerSettings(
ConfigProvider configProvider,
String ddprofUnavailableReason,
ControllerContext.Snapshot context,
boolean hasJfrStackDepthApplied) {
super(configProvider, ddprofUnavailableReason, hasJfrStackDepthApplied);
super(configProvider, context.getDatadogProfilerUnavailableReason(), hasJfrStackDepthApplied);
this.jfrImplementation =
Platform.isNativeImage()
? "native-image"
: (JavaVirtualMachine.isOracleJDK8() ? "oracle" : "openjdk");
this.isDdprofActive = context.isDatadogProfilerEnabled();
}

public void publish() {
if (new ProfilerSettingEvent(null, null, null).isEnabled()) {
new ProfilerSettingEvent(VERSION_KEY, VersionInfo.VERSION).commit();
new ProfilerSettingEvent(UPLOAD_PERIOD_KEY, String.valueOf(uploadPeriod), "seconds").commit();
new ProfilerSettingEvent(UPLOAD_TIMEOUT_KEY, String.valueOf(uploadTimeout), "seconds")
.commit();
Expand Down Expand Up @@ -56,8 +61,16 @@ public void publish() {
new ProfilerSettingEvent(PERF_EVENTS_PARANOID_KEY, perfEventsParanoid).commit();
new ProfilerSettingEvent(NATIVE_STACKS_KEY, String.valueOf(hasNativeStacks)).commit();
new ProfilerSettingEvent(JFR_IMPLEMENTATION_KEY, jfrImplementation).commit();
if (hasJfrStackDepthApplied) {
new ProfilerSettingEvent(STACK_DEPTH_KEY, String.valueOf(stackDepth)).commit();
new ProfilerSettingEvent(
"JFR " + STACK_DEPTH_KEY,
String.valueOf(hasJfrStackDepthApplied ? requestedStackDepth : jfrStackDepth))
.commit();
if (isDdprofActive) {
// emit this setting only if datadog profiler is also active
new ProfilerSettingEvent(
"ddprof " + STACK_DEPTH_KEY,
Comment thread
AlexeyKuznetsov-DD marked this conversation as resolved.
String.valueOf(hasJfrStackDepthApplied ? requestedStackDepth : jfrStackDepth))
.commit();
}
new ProfilerSettingEvent(SELINUX_STATUS_KEY, seLinuxStatus).commit();
if (ddprofUnavailableReason != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ public class OpenJdkOngoingRecording implements OngoingRecording {
recording.start();
log.debug("Recording {} started", recordingName);
this.configMemento =
new JfrProfilerSettings(
configProvider,
context.getDatadogProfilerUnavailableReason(),
jfrStackDepthSettingApplied);
new JfrProfilerSettings(configProvider, context, jfrStackDepthSettingApplied);
}

OpenJdkOngoingRecording(
Expand All @@ -77,10 +74,7 @@ public class OpenJdkOngoingRecording implements OngoingRecording {
recording.start();
log.debug("Recording {} started", recording.getName());
this.configMemento =
new JfrProfilerSettings(
ConfigProvider.getInstance(),
context.getDatadogProfilerUnavailableReason(),
jfrStackDepthSettingApplied);
new JfrProfilerSettings(ConfigProvider.getInstance(), context, jfrStackDepthSettingApplied);
}

private void disableOverriddenEvents(ControllerContext.Snapshot context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ excludedClassesCoverage += [
dependencies {
api libs.slf4j
api project(':internal-api')
api project(':components:environment')
api project(':dd-java-agent:agent-profiling:profiling-utils')

testImplementation libs.bundles.junit5
Expand Down
Loading