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)) {
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;";
JavaValue result(T_OBJECT);
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);
}
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;";
JavaThread* thread = JavaThread::current();
JavaValue result(T_OBJECT);
JfrJavaArguments printHelp(&result, javaClass(), "printHelp", signature, thread);
JfrJavaArguments printHelp(&result, javaClass(), help_method, signature, thread);
invoke(printHelp, 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) {
assert(array != nullptr, "invariant");
DCmdArgumentInfo * const dummy = new DCmdArgumentInfo(nullptr,

View File

@ -37,6 +37,7 @@ class JfrDCmd : public DCmd {
JfrDCmd(outputStream* output, bool heap, int num_arguments);
virtual const char* javaClass() const = 0;
void invoke(JfrJavaArguments& method, TRAPS) const;
void print_java_help(const char* help_method) const;
public:
virtual void execute(DCmdSource source, TRAPS);
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.JFCModelException;
import jdk.jfr.internal.jfc.model.XmlInput;
import jdk.jfr.internal.util.Utils;
/**
* JFR.start
@ -317,11 +318,35 @@ final class DCmdStart extends AbstractDCmd {
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
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
return """
Syntax : JFR.start [options]
Syntax : $SYNTAX
Options:
@ -352,8 +377,8 @@ final class DCmdStart extends AbstractDCmd {
generated from the PID and the current date in the specified
directory. (STRING, no default value)
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
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
'yyyy_MM_dd_HH_mm_ss' format.
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,
i.e. 20s. For more information about the settings syntax, see Javadoc of the
jdk.jfr package.
%s
Options must be specified using the <key> or <key>=<value> syntax.
$JFC_OPTIONS
Options must be specified using the <key> or <key>=<value> syntax. Multiple
options are separated with a $DELIMITER_NAME.
Example usage:
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.start filename=dump.jfr
$ jcmd <pid> JFR.start filename=%s
$ jcmd <pid> JFR.start dumponexit=true
$ jcmd <pid> JFR.start maxage=1h maxsize=1000M
$ jcmd <pid> JFR.start settings=profile
$ jcmd <pid> JFR.start delay=5m settings=my.jfc
$ jcmd <pid> JFR.start gc=high method-profiling=high
$ jcmd <pid> JFR.start jdk.JavaMonitorEnter#threshold=1ms
$ jcmd <pid> JFR.start +HelloWorld#enabled=true +HelloWorld#stackTrace=true
$ jcmd <pid> JFR.start settings=user.jfc com.example.UserDefined#enabled=true
$ jcmd <pid> JFR.start settings=none +Hello#enabled=true
$SOURCE
$SOURCEfilename=dump.jfr
$SOURCEfilename=$DIRECTORY
$SOURCEdumponexit=true
$SOURCEmaxage=1h$DELIMITERmaxsize=1000M
$SOURCEsettings=profile
$SOURCEdelay=5m$DELIMITERsettings=my.jfc
$SOURCEgc=high$DELIMITERmethod-profiling=high
$SOURCEjdk.JavaMonitorEnter#threshold=1ms
$SOURCE+HelloWorld#enabled=true$DELIMITER+HelloWorld#stackTrace=true
$SOURCEsettings=user.jfc$DELIMITERcom.example.UserDefined#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() {

View File

@ -37,6 +37,7 @@ import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@ -445,4 +446,26 @@ public final class Utils {
public static String makeSimpleName(String qualified) {
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);
}
}