Skip to content

Commit

Permalink
[GR-51629] Add support for JFR -XX:FlightRecorderOptions.
Browse files Browse the repository at this point in the history
PullRequest: graal/16742
  • Loading branch information
christianhaeubl committed Jan 26, 2024
2 parents 7e314cf + 81dc9cd commit d3480fe
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 36 deletions.
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

0 comments on commit d3480fe

Please sign in to comment.