8326116: JFR: Add help option to -XX:StartFlightRecording

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2024-04-16 17:21:47 +00:00
parent 941bee197f
commit f7c84137b5
5 changed files with 127 additions and 21 deletions

View File

@ -220,6 +220,11 @@ void JfrDCmd::execute(DCmdSource source, TRAPS) {
if (invalid_state(output(), THREAD)) { if (invalid_state(output(), THREAD)) {
return; return;
} }
if (source == DCmd_Source_Internal && _args != nullptr && strcmp(_args, "help") == 0) {
print_java_help("printStartupHelp");
vm_exit(0);
}
static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;C)[Ljava/lang/String;"; static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;C)[Ljava/lang/String;";
JavaValue result(T_OBJECT); JavaValue result(T_OBJECT);
JfrJavaArguments execute(&result, javaClass(), "execute", signature, CHECK); JfrJavaArguments execute(&result, javaClass(), "execute", signature, CHECK);
@ -241,15 +246,19 @@ void JfrDCmd::execute(DCmdSource source, TRAPS) {
handle_dcmd_result(output(), result.get_oop(), source, THREAD); handle_dcmd_result(output(), result.get_oop(), source, THREAD);
} }
void JfrDCmd::print_help(const char* name) const { void JfrDCmd::print_java_help(const char* help_method) const {
static const char signature[] = "()[Ljava/lang/String;"; static const char signature[] = "()[Ljava/lang/String;";
JavaThread* thread = JavaThread::current(); JavaThread* thread = JavaThread::current();
JavaValue result(T_OBJECT); JavaValue result(T_OBJECT);
JfrJavaArguments printHelp(&result, javaClass(), "printHelp", signature, thread); JfrJavaArguments printHelp(&result, javaClass(), help_method, signature, thread);
invoke(printHelp, thread); invoke(printHelp, thread);
handle_dcmd_result(output(), result.get_oop(), DCmd_Source_MBean, thread); handle_dcmd_result(output(), result.get_oop(), DCmd_Source_MBean, thread);
} }
void JfrDCmd::print_help(const char* name) const {
print_java_help("printHelp");
}
static void initialize_dummy_descriptors(GrowableArray<DCmdArgumentInfo*>* array) { static void initialize_dummy_descriptors(GrowableArray<DCmdArgumentInfo*>* array) {
assert(array != nullptr, "invariant"); assert(array != nullptr, "invariant");
DCmdArgumentInfo * const dummy = new DCmdArgumentInfo(nullptr, DCmdArgumentInfo * const dummy = new DCmdArgumentInfo(nullptr,

View File

@ -37,6 +37,7 @@ class JfrDCmd : public DCmd {
JfrDCmd(outputStream* output, bool heap, int num_arguments); JfrDCmd(outputStream* output, bool heap, int num_arguments);
virtual const char* javaClass() const = 0; virtual const char* javaClass() const = 0;
void invoke(JfrJavaArguments& method, TRAPS) const; void invoke(JfrJavaArguments& method, TRAPS) const;
void print_java_help(const char* help_method) const;
public: public:
virtual void execute(DCmdSource source, TRAPS); virtual void execute(DCmdSource source, TRAPS);
virtual void print_help(const char* name) const; virtual void print_help(const char* name) const;

View File

@ -55,6 +55,7 @@ import jdk.jfr.internal.jfc.JFC;
import jdk.jfr.internal.jfc.model.JFCModel; import jdk.jfr.internal.jfc.model.JFCModel;
import jdk.jfr.internal.jfc.model.JFCModelException; import jdk.jfr.internal.jfc.model.JFCModelException;
import jdk.jfr.internal.jfc.model.XmlInput; import jdk.jfr.internal.jfc.model.XmlInput;
import jdk.jfr.internal.util.Utils;
/** /**
* JFR.start * JFR.start
@ -317,11 +318,35 @@ final class DCmdStart extends AbstractDCmd {
return false; return false;
} }
public String[] printStartupHelp() {
Map<String, String> parameters = Map.of(
"$SYNTAX", "-XX:StartFlightRecording:[options]",
"$SOURCE", "-XX:StartFlightRecording:",
"$DELIMITER", ",",
"$DELIMITER_NAME", "comma",
"$DIRECTORY", exampleDirectory(),
"$JFC_OPTIONS", jfcOptions()
);
return Utils.format(helpTemplate(), parameters).lines().toArray(String[]::new);
}
@Override @Override
public String[] printHelp() { public String[] printHelp() {
Map<String, String> parameters = Map.of(
"$SYNTAX", "JFR.start [options]",
"$SOURCE", "$ jcmd <pid> JFR.start ",
"$DELIMITER", " ",
"$DELIMITER_NAME", "whitespace",
"$DIRECTORY", exampleDirectory(),
"$JFC_OPTIONS", jfcOptions()
);
return Utils.format(helpTemplate(), parameters).lines().toArray(String[]::new);
}
private static String helpTemplate() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890 // 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """ return """
Syntax : JFR.start [options] Syntax : $SYNTAX
Options: Options:
@ -352,8 +377,8 @@ final class DCmdStart extends AbstractDCmd {
generated from the PID and the current date in the specified generated from the PID and the current date in the specified
directory. (STRING, no default value) directory. (STRING, no default value)
Note: If a filename is given, '%%p' in the filename will be Note: If a filename is given, '%p' in the filename will be
replaced by the PID, and '%%t' will be replaced by the time in replaced by the PID, and '%t' will be replaced by the time in
'yyyy_MM_dd_HH_mm_ss' format. 'yyyy_MM_dd_HH_mm_ss' format.
maxage (Optional) Maximum time to keep the recorded data on disk. This maxage (Optional) Maximum time to keep the recorded data on disk. This
@ -409,27 +434,28 @@ final class DCmdStart extends AbstractDCmd {
take precedence. The whitespace character can be omitted for timespan values, take precedence. The whitespace character can be omitted for timespan values,
i.e. 20s. For more information about the settings syntax, see Javadoc of the i.e. 20s. For more information about the settings syntax, see Javadoc of the
jdk.jfr package. jdk.jfr package.
%s $JFC_OPTIONS
Options must be specified using the <key> or <key>=<value> syntax. Options must be specified using the <key> or <key>=<value> syntax. Multiple
options are separated with a $DELIMITER_NAME.
Example usage: Example usage:
$ jcmd <pid> JFR.start $SOURCE
$ jcmd <pid> JFR.start filename=dump.jfr $SOURCEfilename=dump.jfr
$ jcmd <pid> JFR.start filename=%s $SOURCEfilename=$DIRECTORY
$ jcmd <pid> JFR.start dumponexit=true $SOURCEdumponexit=true
$ jcmd <pid> JFR.start maxage=1h maxsize=1000M $SOURCEmaxage=1h$DELIMITERmaxsize=1000M
$ jcmd <pid> JFR.start settings=profile $SOURCEsettings=profile
$ jcmd <pid> JFR.start delay=5m settings=my.jfc $SOURCEdelay=5m$DELIMITERsettings=my.jfc
$ jcmd <pid> JFR.start gc=high method-profiling=high $SOURCEgc=high$DELIMITERmethod-profiling=high
$ jcmd <pid> JFR.start jdk.JavaMonitorEnter#threshold=1ms $SOURCEjdk.JavaMonitorEnter#threshold=1ms
$ jcmd <pid> JFR.start +HelloWorld#enabled=true +HelloWorld#stackTrace=true $SOURCE+HelloWorld#enabled=true$DELIMITER+HelloWorld#stackTrace=true
$ jcmd <pid> JFR.start settings=user.jfc com.example.UserDefined#enabled=true $SOURCEsettings=user.jfc$DELIMITERcom.example.UserDefined#enabled=true
$ jcmd <pid> JFR.start settings=none +Hello#enabled=true $SOURCEsettings=none$DELIMITER+Hello#enabled=true
Note, if the default event settings are modified, overhead may exceed 1%%. Note, if the default event settings are modified, overhead may exceed 1%.
""".formatted(jfcOptions(), exampleDirectory()).lines().toArray(String[]::new); """;
} }
private static String jfcOptions() { private static String jfcOptions() {

View File

@ -37,6 +37,7 @@ import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -445,4 +446,26 @@ public final class Utils {
public static String makeSimpleName(String qualified) { public static String makeSimpleName(String qualified) {
return qualified.substring(qualified.lastIndexOf(".") + 1); return qualified.substring(qualified.lastIndexOf(".") + 1);
} }
public static String format(String template, Map<String, String> parameters) {
StringBuilder sb = new StringBuilder(3 * template.length() / 2);
List<String> keys = new ArrayList<>(parameters.keySet());
// Sort so longest keys are checked first in case keys overlap.
keys.sort((a, b) -> b.length() - a.length());
for (int i = 0; i < template.length(); i++) {
int index = i;
for (int j = 0; j < keys.size(); j++) {
String key = keys.get(j);
if (template.startsWith(key, i)) {
sb.append(parameters.get(key));
i += key.length() - 1;
break;
}
}
if (i == index) {
sb.append(template.charAt(i));
}
}
return sb.toString();
}
} }

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.startupargs;
import jdk.jfr.Recording;
import jdk.test.lib.Asserts;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
/**
* @test
* @key jfr
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @run main jdk.jfr.startupargs.TestStartHelp
*/
public class TestStartHelp {
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder("-XX:StartFlightRecording:help");
OutputAnalyzer out = ProcessTools.executeProcess(pb);
out.shouldContain("Syntax : -XX:StartFlightRecording:[options]");
out.shouldContain("options are separated with a comma.");
out.shouldHaveExitValue(0);
}
}