Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-51629] Add support for JFR -XX:FlightRecorderOptions. #8254

Merged
merged 6 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -918,6 +918,9 @@ public Boolean getValue(OptionValues values) {
@Option(help = "file:doc-files/FlightRecorderLoggingHelp.txt")//
public static final RuntimeOptionKey<String> FlightRecorderLogging = new RuntimeOptionKey<>("all=warning", Immutable);

@Option(help = "file:doc-files/FlightRecorderOptionsHelp.txt")//
public static final RuntimeOptionKey<String> FlightRecorderOptions = new RuntimeOptionKey<>("", Immutable);

public static String reportsPath() {
Path reportsPath = ImageSingletons.lookup(ReportingSupport.class).reportsPath;
if (reportsPath.isAbsolute()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Usage: -XX:FlightRecorderOptions=[option[=value][,...]]

This option expects a comma separated list of key-value pairs. None of the options are mandatory. Possible option keys are as follows:

globalbuffercount=20 (Optional) Number of global JFR buffers. This is a legacy option.
This value cannot be changed once JFR has been initialized.
The default value is determined by the values for memorysize and globalbuffersize.

globalbuffersize=512k (Optional) Size of each global JFR buffer. This is a legacy option.
This value cannot be changed once JFR has been initialized.
The default value is determined by the values for memorysize and globalbuffercount.

maxchunksize=12m (Optional) Maximum size of each individual JFR data chunk.
This value cannot be changed once JFR has been initialized.

memorysize=10m (Optional) Total size of all global JFR buffers.
This value cannot be changed once JFR has been initialized.

repositorypath=... (Optional) Path to the location where JFR recordings are stored until they are
written to a permanent file.
The default location is the temporary directory for the operating system.

stackdepth=64 (Optional) Stack depth for stack traces.
Setting this value greater than the default may cause a performance degradation.
This value cannot be changed once JFR has been initialized.

thread_buffer_size=8k (Optional) Size of each thread-local JFR buffer.
This value cannot be changed once JFR has been initialized.

preserve-repository=false (Optional) Preserve files stored in the disk repository after the process exits.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.OldObjectSample;
import jdk.jfr.internal.Options;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Repository;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.jfc.JFC;

Expand All @@ -74,10 +76,6 @@ public JfrManager(boolean hostedEnabled) {
this.hostedEnabled = hostedEnabled;
}

public static boolean isJFREnabled() {
return SubstrateOptions.FlightRecorder.getValue() || !SubstrateOptions.StartFlightRecording.getValue().isEmpty();
}

@Fold
public static JfrManager get() {
return ImageSingletons.lookup(JfrManager.class);
Expand All @@ -87,7 +85,9 @@ public RuntimeSupport.Hook startupHook() {
return isFirstIsolate -> {
parseFlightRecorderLogging(SubstrateOptions.FlightRecorderLogging.getValue());
periodicEventSetup();
if (isJFREnabled()) {

boolean startRecording = SubstrateOptions.FlightRecorder.getValue() || !SubstrateOptions.StartFlightRecording.getValue().isEmpty();
if (startRecording) {
initRecording();
}
};
Expand Down Expand Up @@ -116,17 +116,27 @@ private static void periodicEventSetup() throws SecurityException {
}

private static void initRecording() {
Map<JfrStartArgument, String> args = parseStartFlightRecording();
String name = args.get(JfrStartArgument.Name);
String[] settings = parseSettings(args);
Long delay = parseDuration(args, JfrStartArgument.Delay);
Long duration = parseDuration(args, JfrStartArgument.Duration);
Boolean disk = parseBoolean(args, JfrStartArgument.Disk);
String path = args.get(JfrStartArgument.Filename);
Long maxAge = parseDuration(args, JfrStartArgument.MaxAge);
Long maxSize = parseMaxSize(args, JfrStartArgument.MaxSize);
Boolean dumpOnExit = parseBoolean(args, JfrStartArgument.DumpOnExit);
Boolean pathToGcRoots = parseBoolean(args, JfrStartArgument.PathToGCRoots);
Map<JfrArgument, String> startArgs = parseJfrOptions(SubstrateOptions.StartFlightRecording.getValue(), JfrStartArgument.values());
Map<JfrArgument, String> optionsArgs = parseJfrOptions(SubstrateOptions.FlightRecorderOptions.getValue(), FlightRecorderOptionsArgument.values());

String name = startArgs.get(JfrStartArgument.Name);
String[] settings = parseSettings(startArgs);
Long delay = parseDuration(startArgs, JfrStartArgument.Delay);
Long duration = parseDuration(startArgs, JfrStartArgument.Duration);
Boolean disk = parseBoolean(startArgs, JfrStartArgument.Disk);
String path = startArgs.get(JfrStartArgument.Filename);
Long maxAge = parseDuration(startArgs, JfrStartArgument.MaxAge);
Long maxSize = parseMaxSize(startArgs, JfrStartArgument.MaxSize);
Boolean dumpOnExit = parseBoolean(startArgs, JfrStartArgument.DumpOnExit);
Boolean pathToGcRoots = parseBoolean(startArgs, JfrStartArgument.PathToGCRoots);
String stackDepth = optionsArgs.get(FlightRecorderOptionsArgument.StackDepth);
Long maxChunkSize = parseMaxSize(optionsArgs, FlightRecorderOptionsArgument.MaxChunkSize);
Long memorySize = parseMaxSize(optionsArgs, FlightRecorderOptionsArgument.MemorySize);
Long globalBufferSize = parseMaxSize(optionsArgs, FlightRecorderOptionsArgument.GlobalBufferSize);
Long globalBufferCount = parseMaxSize(optionsArgs, FlightRecorderOptionsArgument.GlobalBufferCount);
Long threadBufferSize = parseMaxSize(optionsArgs, FlightRecorderOptionsArgument.ThreadBufferSize);
Boolean preserveRepo = parseBoolean(optionsArgs, FlightRecorderOptionsArgument.PreserveRepository);
String repositoryPath = optionsArgs.get(FlightRecorderOptionsArgument.RepositoryPath);

try {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Expand Down Expand Up @@ -183,6 +193,47 @@ private static void initRecording() {
}
}

if (stackDepth != null) {
try {
Options.setStackDepth(Integer.valueOf(stackDepth));
} catch (Throwable e) {
throw new Exception("Could not start recording, stack depth is not an integer.", e);
}
}

if (repositoryPath != null) {
try {
SecuritySupport.SafePath repositorySafePath = new SecuritySupport.SafePath(repositoryPath);
Repository.getRepository().setBasePath(repositorySafePath);
} catch (Throwable e) {
throw new Exception("Could not start recording, repository path is invalid.", e);
}
}

if (maxChunkSize != null) {
Options.setMaxChunkSize(maxChunkSize);
}

if (preserveRepo != null) {
Options.setPreserveRepository(preserveRepo);
}

if (threadBufferSize != null) {
Options.setThreadBufferSize(threadBufferSize);
}

if (globalBufferCount != null) {
Options.setGlobalBufferCount(globalBufferCount);
}

if (globalBufferSize != null) {
Options.setGlobalBufferSize(globalBufferSize);
}

if (memorySize != null) {
Options.setMemorySize(memorySize);
}

Recording recording = new Recording();
if (name != null) {
recording.setName(name);
Expand Down Expand Up @@ -298,15 +349,13 @@ private static String getPath(Path path) {
}
}

private static Map<JfrStartArgument, String> parseStartFlightRecording() {
Map<JfrStartArgument, String> optionsMap = new HashMap<>();
String text = SubstrateOptions.StartFlightRecording.getValue();
if (!text.isEmpty()) {
JfrStartArgument[] possibleArguments = JfrStartArgument.values();
String[] options = text.split(",");
private static Map<JfrArgument, String> parseJfrOptions(String userInput, JfrArgument[] possibleArguments) {
Map<JfrArgument, String> optionsMap = new HashMap<>();
if (!userInput.isEmpty()) {
String[] options = userInput.split(",");
for (String option : options) {
String[] keyVal = option.split("=");
JfrStartArgument arg = findArgument(possibleArguments, keyVal[0]);
JfrArgument arg = findArgument(possibleArguments, keyVal[0]);
if (arg == null) {
throw VMError.shouldNotReachHere("Unknown argument '" + keyVal[0] + "' in " + SubstrateOptions.StartFlightRecording.getName());
}
Expand All @@ -316,7 +365,7 @@ private static Map<JfrStartArgument, String> parseStartFlightRecording() {
return optionsMap;
}

private static String[] parseSettings(Map<JfrStartArgument, String> args) throws UserException {
private static String[] parseSettings(Map<JfrArgument, String> args) throws UserException {
String settings = args.get(JfrStartArgument.Settings);
if (settings == null) {
return new String[]{DEFAULT_JFC_NAME};
Expand All @@ -328,7 +377,7 @@ private static String[] parseSettings(Map<JfrStartArgument, String> args) throws
}

@SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null allowed as return value")
private static Boolean parseBoolean(Map<JfrStartArgument, String> args, JfrStartArgument key) throws IllegalArgumentException {
private static Boolean parseBoolean(Map<JfrArgument, String> args, JfrArgument key) throws IllegalArgumentException {
String value = args.get(key);
if (value == null) {
return null;
Expand All @@ -337,11 +386,11 @@ private static Boolean parseBoolean(Map<JfrStartArgument, String> args, JfrStart
} else if ("false".equalsIgnoreCase(value)) {
return false;
} else {
throw VMError.shouldNotReachHere("Could not parse JFR argument '" + key.cmdLineKey + "=" + value + "'. Expected a boolean value.");
throw VMError.shouldNotReachHere("Could not parse JFR argument '" + key.getCmdLineKey() + "=" + value + "'. Expected a boolean value.");
}
}

private static Long parseDuration(Map<JfrStartArgument, String> args, JfrStartArgument key) {
private static Long parseDuration(Map<JfrArgument, String> args, JfrStartArgument key) {
String value = args.get(key);
if (value != null) {
try {
Expand Down Expand Up @@ -385,7 +434,7 @@ private static Long parseDuration(Map<JfrStartArgument, String> args, JfrStartAr
return null;
}

private static Long parseMaxSize(Map<JfrStartArgument, String> args, JfrStartArgument key) {
private static Long parseMaxSize(Map<JfrArgument, String> args, JfrArgument key) {
final String value = args.get(key);
if (value != null) {
try {
Expand Down Expand Up @@ -418,7 +467,7 @@ private static Long parseMaxSize(Map<JfrStartArgument, String> args, JfrStartArg
return number;
}
} catch (IllegalArgumentException e) {
throw VMError.shouldNotReachHere("Could not parse JFR argument '" + key.cmdLineKey + "=" + value + "'. " + e.getMessage());
throw VMError.shouldNotReachHere("Could not parse JFR argument '" + key.getCmdLineKey() + "=" + value + "'. " + e.getMessage());
}
}
return null;
Expand All @@ -432,16 +481,20 @@ private static int indexOfFirstNonDigitCharacter(String durationText) {
return idx;
}

private static JfrStartArgument findArgument(JfrStartArgument[] possibleArguments, String value) {
for (JfrStartArgument arg : possibleArguments) {
if (arg.cmdLineKey.equals(value)) {
private static JfrArgument findArgument(JfrArgument[] possibleArguments, String value) {
for (JfrArgument arg : possibleArguments) {
if (arg.getCmdLineKey().equals(value)) {
return arg;
}
}
return null;
}

private enum JfrStartArgument {
private interface JfrArgument {
String getCmdLineKey();
}

private enum JfrStartArgument implements JfrArgument {
Name("name"),
Settings("settings"),
Delay("delay"),
Expand All @@ -458,5 +511,32 @@ private enum JfrStartArgument {
JfrStartArgument(String key) {
this.cmdLineKey = key;
}

@Override
public String getCmdLineKey() {
return cmdLineKey;
}
}

private enum FlightRecorderOptionsArgument implements JfrArgument {
GlobalBufferCount("globalbuffercount"),
GlobalBufferSize("globalbuffersize"),
MaxChunkSize("maxchunksize"),
MemorySize("memorysize"),
RepositoryPath("repositorypath"),
StackDepth("stackdepth"),
ThreadBufferSize("thread_buffer_size"),
PreserveRepository("preserve-repository");

private final String cmdLineKey;

FlightRecorderOptionsArgument(String key) {
this.cmdLineKey = key;
}

@Override
public String getCmdLineKey() {
return cmdLineKey;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,25 @@
*/
package com.oracle.svm.core.jfr;

import jdk.graal.compiler.core.common.NumUtil;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.os.VirtualMemoryProvider;

import jdk.graal.compiler.core.common.NumUtil;
import jdk.jfr.internal.Options;

/**
* Holds all JFR-related options that can be set by the user. It is also used to validate and adjust
* the option values as needed.
*
* Similar to OpenJDK, the options set via -XX:FlightRecorderOptions cannot be changed after JFR is
* initialized. This means that after {@link SubstrateJVM#createJFR(boolean)} is called, this class
* is no longer needed.
*
* This class is used to store options set at the OpenJDK Java-level which propagate down to the VM
* level via {@link jdk.jfr.internal.JVM}. The option values are stored here until they are
* eventually used when first recording is created and JFR is initialized.
*/
public class JfrOptionSet {
private static final int MEMORY_SIZE = 1;
Expand All @@ -54,6 +62,7 @@ public class JfrOptionSet {
public final JfrOptionLong globalBufferCount;
public final JfrOptionLong memorySize;
public final JfrOptionLong maxChunkSize;
public final JfrOptionLong stackDepth;

@Platforms(Platform.HOSTED_ONLY.class)
public JfrOptionSet() {
Expand All @@ -62,6 +71,7 @@ public JfrOptionSet() {
globalBufferCount = new JfrOptionLong(Options.getGlobalBufferCount());
memorySize = new JfrOptionLong(Options.getMemorySize());
maxChunkSize = new JfrOptionLong(Options.getMaxChunkSize());
stackDepth = new JfrOptionLong(Options.getStackDepth());
}

public void validateAndAdjustMemoryOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import java.util.List;

import com.oracle.svm.core.log.Log;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
Expand All @@ -39,6 +38,7 @@
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.jfr.logging.JfrLogging;
import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.sampler.SamplerBufferPool;
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
import com.oracle.svm.core.thread.JavaThreads;
Expand Down Expand Up @@ -209,6 +209,7 @@ public boolean createJFR(boolean simulateFailure) {
threadLocal.initialize(options.threadBufferSize.getValue());
globalMemory.initialize(options.globalBufferSize.getValue(), options.globalBufferCount.getValue());
unlockedChunkWriter.initialize(options.maxChunkSize.getValue());
stackTraceRepo.setStackTraceDepth(NumUtil.safeToInt(options.stackDepth.getValue()));

recorderThread.start();

Expand Down Expand Up @@ -444,7 +445,7 @@ public void setCompressedIntegers(boolean compressed) {
* See {@link JVM#setStackDepth}.
*/
public void setStackDepth(int depth) {
stackTraceRepo.setStackTraceDepth(depth);
options.stackDepth.setUserValue(depth);
}

/**
Expand Down
Loading