8265271: JFR: Allow use of .jfc options when starting JFR

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2021-06-10 13:18:54 +00:00
parent f677163b8a
commit f716711c7b
22 changed files with 1362 additions and 630 deletions

View File

@ -43,17 +43,16 @@
#include "services/diagnosticFramework.hpp"
#include "utilities/globalDefinitions.hpp"
#ifdef _WINDOWS
#define JFR_FILENAME_EXAMPLE "C:\\Users\\user\\My Recording.jfr"
#endif
#ifdef __APPLE__
#define JFR_FILENAME_EXAMPLE "/Users/user/My Recording.jfr"
#endif
#ifndef JFR_FILENAME_EXAMPLE
#define JFR_FILENAME_EXAMPLE "/home/user/My Recording.jfr"
#endif
bool register_jfr_dcmds() {
uint32_t full_export = DCmd_Source_Internal | DCmd_Source_AttachAPI | DCmd_Source_MBean;
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrCheckFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrDumpFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStartFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStopFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrConfigureFlightRecorderDCmd>(full_export, true, false));
return true;
}
// JNIHandle management
@ -116,17 +115,6 @@ static bool is_disabled(outputStream* output) {
return false;
}
static bool is_recorder_instance_created(outputStream* output) {
if (!JfrRecorder::is_created()) {
if (output != NULL) {
output->print_cr("No available recordings.\n");
output->print_cr("Use JFR.start to start a recording.\n");
}
return false;
}
return true;
}
static bool invalid_state(outputStream* out, TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
return is_disabled(out) || !is_module_available(out, THREAD);
@ -186,6 +174,7 @@ static void handle_dcmd_result(outputStream* output,
TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
assert(output != NULL, "invariant");
ResourceMark rm(THREAD);
const bool startup = DCmd_Source_Internal == source;
if (HAS_PENDING_EXCEPTION) {
handle_pending_exception(output, startup, PENDING_EXCEPTION);
@ -225,380 +214,79 @@ static oop construct_dcmd_instance(JfrJavaArguments* args, TRAPS) {
return args->result()->get_oop();
}
JfrDumpFlightRecordingDCmd::JfrDumpFlightRecordingDCmd(outputStream* output,
bool heap) : DCmdWithParser(output, heap),
_name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", false, NULL),
_filename("filename", "Copy recording data to file, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false),
_maxage("maxage", "Maximum duration to dump, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"),
_maxsize("maxsize", "Maximum amount of bytes to dump, in (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"),
_begin("begin", "Point in time to dump data from, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false),
_end("end", "Point in time to dump data to, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false),
_path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") {
_dcmdparser.add_dcmd_option(&_name);
_dcmdparser.add_dcmd_option(&_filename);
_dcmdparser.add_dcmd_option(&_maxage);
_dcmdparser.add_dcmd_option(&_maxsize);
_dcmdparser.add_dcmd_option(&_begin);
_dcmdparser.add_dcmd_option(&_end);
_dcmdparser.add_dcmd_option(&_path_to_gc_roots);
};
int JfrDumpFlightRecordingDCmd::num_arguments() {
ResourceMark rm;
JfrDumpFlightRecordingDCmd* dcmd = new JfrDumpFlightRecordingDCmd(NULL, false);
if (dcmd != NULL) {
DCmdMark mark(dcmd);
return dcmd->_dcmdparser.num_arguments();
}
return 0;
}
void JfrDumpFlightRecordingDCmd::execute(DCmdSource source, TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) {
return;
}
void JfrDCmd::invoke(JfrJavaArguments& method, TRAPS) const {
JavaValue constructor_result(T_OBJECT);
JfrJavaArguments constructor_args(&constructor_result);
constructor_args.set_klass(javaClass(), CHECK);
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JNIHandleBlockManager jni_handle_management(THREAD);
JavaValue result(T_OBJECT);
JfrJavaArguments constructor_args(&result);
constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdDump", CHECK);
const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK);
Handle h_dcmd_instance(THREAD, dcmd);
assert(h_dcmd_instance.not_null(), "invariant");
jstring name = NULL;
if (_name.is_set() && _name.value() != NULL) {
name = JfrJavaSupport::new_string(_name.value(), CHECK);
}
jstring filepath = NULL;
if (_filename.is_set() && _filename.value() != NULL) {
filepath = JfrJavaSupport::new_string(_filename.value(), CHECK);
}
jobject maxage = NULL;
if (_maxage.is_set()) {
maxage = JfrJavaSupport::new_java_lang_Long(_maxage.value()._nanotime, CHECK);
}
jobject maxsize = NULL;
if (_maxsize.is_set()) {
maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK);
}
jstring begin = NULL;
if (_begin.is_set() && _begin.value() != NULL) {
begin = JfrJavaSupport::new_string(_begin.value(), CHECK);
}
jstring end = NULL;
if (_end.is_set() && _end.value() != NULL) {
end = JfrJavaSupport::new_string(_end.value(), CHECK);
}
jobject path_to_gc_roots = NULL;
if (_path_to_gc_roots.is_set()) {
path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK);
}
static const char klass[] = "jdk/jfr/internal/dcmd/DCmdDump";
static const char method[] = "execute";
static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)[Ljava/lang/String;";
JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
execute_args.set_receiver(h_dcmd_instance);
// arguments
execute_args.push_jobject(name);
execute_args.push_jobject(filepath);
execute_args.push_jobject(maxage);
execute_args.push_jobject(maxsize);
execute_args.push_jobject(begin);
execute_args.push_jobject(end);
execute_args.push_jobject(path_to_gc_roots);
JfrJavaSupport::call_virtual(&execute_args, THREAD);
handle_dcmd_result(output(), result.get_oop(), source, THREAD);
method.set_receiver(h_dcmd_instance);
JfrJavaSupport::call_virtual(&method, THREAD);
}
JfrCheckFlightRecordingDCmd::JfrCheckFlightRecordingDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap),
_name("name","Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL),
_verbose("verbose","Print event settings for the recording(s)","BOOLEAN",
false, "false") {
_dcmdparser.add_dcmd_option(&_name);
_dcmdparser.add_dcmd_option(&_verbose);
};
int JfrCheckFlightRecordingDCmd::num_arguments() {
ResourceMark rm;
JfrCheckFlightRecordingDCmd* dcmd = new JfrCheckFlightRecordingDCmd(NULL, false);
if (dcmd != NULL) {
DCmdMark mark(dcmd);
return dcmd->_dcmdparser.num_arguments();
}
return 0;
void JfrDCmd::parse(CmdLine* line, char delim, TRAPS) {
_args = line->args_addr();
_delimiter = delim;
// Error checking done in execute.
// Will not matter from DCmdFactory perspective
// where parse and execute are called consecutively.
}
void JfrCheckFlightRecordingDCmd::execute(DCmdSource source, TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) {
return;
}
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JNIHandleBlockManager jni_handle_management(THREAD);
JavaValue result(T_OBJECT);
JfrJavaArguments constructor_args(&result);
constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdCheck", CHECK);
const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK);
Handle h_dcmd_instance(THREAD, dcmd);
assert(h_dcmd_instance.not_null(), "invariant");
jstring name = NULL;
if (_name.is_set() && _name.value() != NULL) {
name = JfrJavaSupport::new_string(_name.value(), CHECK);
}
jobject verbose = NULL;
if (_verbose.is_set()) {
verbose = JfrJavaSupport::new_java_lang_Boolean(_verbose.value(), CHECK);
}
static const char klass[] = "jdk/jfr/internal/dcmd/DCmdCheck";
static const char method[] = "execute";
static const char signature[] = "(Ljava/lang/String;Ljava/lang/Boolean;)[Ljava/lang/String;";
JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
execute_args.set_receiver(h_dcmd_instance);
// arguments
execute_args.push_jobject(name);
execute_args.push_jobject(verbose);
JfrJavaSupport::call_virtual(&execute_args, THREAD);
handle_dcmd_result(output(), result.get_oop(), source, THREAD);
}
JfrStartFlightRecordingDCmd::JfrStartFlightRecordingDCmd(outputStream* output,
bool heap) : DCmdWithParser(output, heap),
_name("name", "Name that can be used to identify recording, e.g. \\\"My Recording\\\"", "STRING", false, NULL),
_settings("settings", "Settings file(s), e.g. profile or default. See JAVA_HOME/lib/jfr", "STRING SET", false),
_delay("delay", "Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h.", "NANOTIME", false, "0"),
_duration("duration", "Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s.", "NANOTIME", false, "0"),
_disk("disk", "Recording should be persisted to disk", "BOOLEAN", false),
_filename("filename", "Resulting recording filename, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false),
_maxage("maxage", "Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"),
_maxsize("maxsize", "Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"),
_flush_interval("flush-interval", "Minimum time before flushing buffers, measured in (s)econds, e.g. 4 s, or 0 for flushing when a recording ends", "NANOTIME", false, "1s"),
_dump_on_exit("dumponexit", "Dump running recording when JVM shuts down", "BOOLEAN", false),
_path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") {
_dcmdparser.add_dcmd_option(&_name);
_dcmdparser.add_dcmd_option(&_settings);
_dcmdparser.add_dcmd_option(&_delay);
_dcmdparser.add_dcmd_option(&_duration);
_dcmdparser.add_dcmd_option(&_disk);
_dcmdparser.add_dcmd_option(&_filename);
_dcmdparser.add_dcmd_option(&_maxage);
_dcmdparser.add_dcmd_option(&_maxsize);
_dcmdparser.add_dcmd_option(&_flush_interval);
_dcmdparser.add_dcmd_option(&_dump_on_exit);
_dcmdparser.add_dcmd_option(&_path_to_gc_roots);
};
int JfrStartFlightRecordingDCmd::num_arguments() {
ResourceMark rm;
JfrStartFlightRecordingDCmd* dcmd = new JfrStartFlightRecordingDCmd(NULL, false);
if (dcmd != NULL) {
DCmdMark mark(dcmd);
return dcmd->_dcmdparser.num_arguments();
}
return 0;
}
void JfrStartFlightRecordingDCmd::execute(DCmdSource source, TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
void JfrDCmd::execute(DCmdSource source, TRAPS) {
static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;C)[Ljava/lang/String;";
if (invalid_state(output(), THREAD)) {
return;
}
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JNIHandleBlockManager jni_handle_management(THREAD);
JavaValue result(T_OBJECT);
JfrJavaArguments constructor_args(&result);
constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdStart", THREAD);
const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK);
Handle h_dcmd_instance(THREAD, dcmd);
assert(h_dcmd_instance.not_null(), "invariant");
jstring name = NULL;
if (_name.is_set() && _name.value() != NULL) {
name = JfrJavaSupport::new_string(_name.value(), CHECK);
JfrJavaArguments execute(&result, javaClass(), "execute", signature, CHECK);
jstring argument = JfrJavaSupport::new_string(_args, CHECK);
jstring s = NULL;
if (source == DCmd_Source_Internal) {
s = JfrJavaSupport::new_string("internal", CHECK);
}
jstring filename = NULL;
if (_filename.is_set() && _filename.value() != NULL) {
filename = JfrJavaSupport::new_string(_filename.value(), CHECK);
if (source == DCmd_Source_MBean) {
s = JfrJavaSupport::new_string("mbean", CHECK);
}
jobject maxage = NULL;
if (_maxage.is_set()) {
maxage = JfrJavaSupport::new_java_lang_Long(_maxage.value()._nanotime, CHECK);
if (source == DCmd_Source_AttachAPI) {
s = JfrJavaSupport::new_string("attach", CHECK);
}
jobject maxsize = NULL;
if (_maxsize.is_set()) {
maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK);
}
jobject flush_interval = NULL;
if (_flush_interval.is_set()) {
flush_interval = JfrJavaSupport::new_java_lang_Long(_flush_interval.value()._nanotime, CHECK);
}
jobject duration = NULL;
if (_duration.is_set()) {
duration = JfrJavaSupport::new_java_lang_Long(_duration.value()._nanotime, CHECK);
}
jobject delay = NULL;
if (_delay.is_set()) {
delay = JfrJavaSupport::new_java_lang_Long(_delay.value()._nanotime, CHECK);
}
jobject disk = NULL;
if (_disk.is_set()) {
disk = JfrJavaSupport::new_java_lang_Boolean(_disk.value(), CHECK);
}
jobject dump_on_exit = NULL;
if (_dump_on_exit.is_set()) {
dump_on_exit = JfrJavaSupport::new_java_lang_Boolean(_dump_on_exit.value(), CHECK);
}
jobject path_to_gc_roots = NULL;
if (_path_to_gc_roots.is_set()) {
path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK);
}
jobjectArray settings = NULL;
if (_settings.is_set()) {
int length = _settings.value()->array()->length();
if (length == 1) {
const char* c_str = _settings.value()->array()->at(0);
if (strcmp(c_str, "none") == 0) {
length = 0;
}
}
settings = JfrJavaSupport::new_string_array(length, CHECK);
assert(settings != NULL, "invariant");
for (int i = 0; i < length; ++i) {
jobject element = JfrJavaSupport::new_string(_settings.value()->array()->at(i), CHECK);
assert(element != NULL, "invariant");
JfrJavaSupport::set_array_element(settings, element, i, CHECK);
}
} else {
settings = JfrJavaSupport::new_string_array(1, CHECK);
assert(settings != NULL, "invariant");
jobject element = JfrJavaSupport::new_string("default", CHECK);
assert(element != NULL, "invariant");
JfrJavaSupport::set_array_element(settings, element, 0, CHECK);
}
static const char klass[] = "jdk/jfr/internal/dcmd/DCmdStart";
static const char method[] = "execute";
static const char signature[] = "(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/Long;"
"Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/String;"
"Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;)[Ljava/lang/String;";
JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
execute_args.set_receiver(h_dcmd_instance);
// arguments
execute_args.push_jobject(name);
execute_args.push_jobject(settings);
execute_args.push_jobject(delay);
execute_args.push_jobject(duration);
execute_args.push_jobject(disk);
execute_args.push_jobject(filename);
execute_args.push_jobject(maxage);
execute_args.push_jobject(maxsize);
execute_args.push_jobject(flush_interval);
execute_args.push_jobject(dump_on_exit);
execute_args.push_jobject(path_to_gc_roots);
JfrJavaSupport::call_virtual(&execute_args, THREAD);
execute.push_jobject(s);
execute.push_jobject(argument);
execute.push_int(_delimiter);
invoke(execute, THREAD);
handle_dcmd_result(output(), result.get_oop(), source, THREAD);
}
JfrStopFlightRecordingDCmd::JfrStopFlightRecordingDCmd(outputStream* output,
bool heap) : DCmdWithParser(output, heap),
_name("name", "Recording text,.e.g \\\"My Recording\\\"", "STRING", true, NULL),
_filename("filename", "Copy recording data to file, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false, NULL) {
_dcmdparser.add_dcmd_option(&_name);
_dcmdparser.add_dcmd_option(&_filename);
};
int JfrStopFlightRecordingDCmd::num_arguments() {
ResourceMark rm;
JfrStopFlightRecordingDCmd* dcmd = new JfrStopFlightRecordingDCmd(NULL, false);
if (dcmd != NULL) {
DCmdMark mark(dcmd);
return dcmd->_dcmdparser.num_arguments();
}
return 0;
void JfrDCmd::print_help(const char* name) const {
static const char signature[] = "()[Ljava/lang/String;";
JavaThread* thread = JavaThread::current();
JavaValue result(T_OBJECT);
JfrJavaArguments print_help(&result, javaClass(), "printHelp", signature, thread);
invoke(print_help, thread);
handle_dcmd_result(output(), result.get_oop(), DCmd_Source_MBean, thread);
}
void JfrStopFlightRecordingDCmd::execute(DCmdSource source, TRAPS) {
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD));
GrowableArray<DCmdArgumentInfo*>* JfrDCmd::argument_info_array() const {
return new GrowableArray<DCmdArgumentInfo*>();
}
if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) {
return;
GrowableArray<const char*>* JfrDCmd::argument_name_array() const {
GrowableArray<DCmdArgumentInfo*>* argument_infos = argument_info_array();
GrowableArray<const char*>* array = new GrowableArray<const char*>(argument_infos->length());
for (int i = 0; i < argument_infos->length(); i++) {
array->append(argument_infos->at(i)->name());
}
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JNIHandleBlockManager jni_handle_management(THREAD);
JavaValue result(T_OBJECT);
JfrJavaArguments constructor_args(&result);
constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdStop", CHECK);
const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK);
Handle h_dcmd_instance(THREAD, dcmd);
assert(h_dcmd_instance.not_null(), "invariant");
jstring name = NULL;
if (_name.is_set() && _name.value() != NULL) {
name = JfrJavaSupport::new_string(_name.value(), CHECK);
}
jstring filepath = NULL;
if (_filename.is_set() && _filename.value() != NULL) {
filepath = JfrJavaSupport::new_string(_filename.value(), CHECK);
}
static const char klass[] = "jdk/jfr/internal/dcmd/DCmdStop";
static const char method[] = "execute";
static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
execute_args.set_receiver(h_dcmd_instance);
// arguments
execute_args.push_jobject(name);
execute_args.push_jobject(filepath);
JfrJavaSupport::call_virtual(&execute_args, THREAD);
handle_dcmd_result(output(), result.get_oop(), source, THREAD);
return array;
}
JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* output,
@ -624,6 +312,64 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out
_dcmdparser.add_dcmd_option(&_sample_threads);
};
void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
outputStream* out = output();
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
out->print_cr("Options:");
out->print_cr("");
out->print_cr(" globalbuffercount (Optional) Number of global buffers. This option is a legacy");
out->print_cr(" option: change the memorysize parameter to alter the number of");
out->print_cr(" global buffers. This value cannot be changed once JFR has been");
out->print_cr(" initalized. (STRING, default determined by the value for");
out->print_cr(" memorysize)");
out->print_cr("");
out->print_cr(" globalbuffersize (Optional) Size of the global buffers, in bytes. This option is a");
out->print_cr(" legacy option: change the memorysize parameter to alter the size");
out->print_cr(" of the global buffers. This value cannot be changed once JFR has");
out->print_cr(" been initalized. (STRING, default determined by the value for");
out->print_cr(" memorysize)");
out->print_cr("");
out->print_cr(" maxchunksize (Optional) Maximum size of an individual data chunk in bytes if");
out->print_cr(" one of the following suffixes is not used: 'm' or 'M' for");
out->print_cr(" megabytes OR 'g' or 'G' for gigabytes. This value cannot be");
out->print_cr(" changed once JFR has been initialized. (STRING, 12M)");
out->print_cr("");
out->print_cr(" memorysize (Optional) Overall memory size, in bytes if one of the following");
out->print_cr(" suffixes is not used: 'm' or 'M' for megabytes OR 'g' or 'G' for");
out->print_cr(" gigabytes. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (STRING, 10M)");
out->print_cr("");
out->print_cr(" repositorypath (Optional) Path to the location where recordings are stored until");
out->print_cr(" they are written to a permanent file. (STRING, The default");
out->print_cr(" location is the temporary directory for the operating system. On");
out->print_cr(" Linux operating systems, the temporary directory is /tmp. On");
out->print_cr(" Windows, the temporary directory is specified by the TMP");
out->print_cr(" environment variable.)");
out->print_cr("");
out->print_cr(" stackdepth (Optional) Stack depth for stack traces. Setting this value");
out->print_cr(" greater than the default of 64 may cause a performance");
out->print_cr(" degradation. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (LONG, 64)");
out->print_cr("");
out->print_cr(" thread_buffer_size (Optional) Local buffer size for each thread in bytes if one of");
out->print_cr(" the following suffixes is not used: 'k' or 'K' for kilobytes or");
out->print_cr(" 'm' or 'M' for megabytes. Overriding this parameter could reduce");
out->print_cr(" performance and is not recommended. This value cannot be changed");
out->print_cr(" once JFR has been initialized. (STRING, 8k)");
out->print_cr("");
out->print_cr(" samplethreads (Optional) Flag for activating thread sampling. (BOOLEAN, true)");
out->print_cr("");
out->print_cr("Options must be specified using the <key> or <key>=<value> syntax.");
out->print_cr("");
out->print_cr("Example usage:");
out->print_cr("");
out->print_cr(" $ jcmd <pid> JFR.configure");
out->print_cr(" $ jcmd <pid> JFR.configure repositorypath=/temporary");
out->print_cr(" $ jcmd <pid> JFR.configure stackdepth=256");
out->print_cr(" $ jcmd <pid> JFR.configure memorysize=100M");
out->print_cr("");
}
int JfrConfigureFlightRecorderDCmd::num_arguments() {
ResourceMark rm;
JfrConfigureFlightRecorderDCmd* dcmd = new JfrConfigureFlightRecorderDCmd(NULL, false);
@ -721,13 +467,3 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) {
JfrJavaSupport::call_virtual(&execute_args, THREAD);
handle_dcmd_result(output(), result.get_oop(), source, THREAD);
}
bool register_jfr_dcmds() {
uint32_t full_export = DCmd_Source_Internal | DCmd_Source_AttachAPI | DCmd_Source_MBean;
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrCheckFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrDumpFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStartFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStopFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrConfigureFlightRecorderDCmd>(full_export, true, false));
return true;
}

View File

@ -26,76 +26,29 @@
#define SHARE_JFR_DCMD_JFRDCMDS_HPP
#include "services/diagnosticCommand.hpp"
class JfrJavaArguments;
class JfrDumpFlightRecordingDCmd : public DCmdWithParser {
protected:
DCmdArgument<char*> _name;
DCmdArgument<char*> _filename;
DCmdArgument<NanoTimeArgument> _maxage;
DCmdArgument<MemorySizeArgument> _maxsize;
DCmdArgument<char*> _begin;
DCmdArgument<char*> _end;
DCmdArgument<bool> _path_to_gc_roots;
class JfrDCmd : public DCmd {
private:
const char* _args;
char _delimiter;
public:
JfrDumpFlightRecordingDCmd(outputStream* output, bool heap);
static const char* name() {
return "JFR.dump";
}
static const char* description() {
return "Copies contents of a JFR recording to file. Either the name or the recording id must be specified.";
}
static const char* impact() {
return "Low";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
static int num_arguments();
JfrDCmd(outputStream* output, bool heap) : DCmd(output,heap), _args(NULL), _delimiter('\0') {}
virtual void execute(DCmdSource source, TRAPS);
virtual void print_help(const char* name) const;
virtual GrowableArray<const char*>* argument_name_array() const;
virtual GrowableArray<DCmdArgumentInfo*>* argument_info_array() const;
virtual void parse(CmdLine* line, char delim, TRAPS);
protected:
virtual const char* javaClass() const = 0;
void invoke(JfrJavaArguments& method, TRAPS) const;
};
class JfrCheckFlightRecordingDCmd : public DCmdWithParser {
protected:
DCmdArgument<char*> _name;
DCmdArgument<bool> _verbose;
class JfrStartFlightRecordingDCmd : public JfrDCmd {
public:
JfrCheckFlightRecordingDCmd(outputStream* output, bool heap);
static const char* name() {
return "JFR.check";
}
static const char* description() {
return "Checks running JFR recording(s)";
}
static const char* impact() {
return "Low";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
static int num_arguments();
virtual void execute(DCmdSource source, TRAPS);
};
JfrStartFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap) {}
class JfrStartFlightRecordingDCmd : public DCmdWithParser {
protected:
DCmdArgument<char*> _name;
DCmdArgument<StringArrayArgument*> _settings;
DCmdArgument<NanoTimeArgument> _delay;
DCmdArgument<NanoTimeArgument> _duration;
DCmdArgument<bool> _disk;
DCmdArgument<char*> _filename;
DCmdArgument<NanoTimeArgument> _maxage;
DCmdArgument<MemorySizeArgument> _maxsize;
DCmdArgument<NanoTimeArgument> _flush_interval;
DCmdArgument<bool> _dump_on_exit;
DCmdArgument<bool> _path_to_gc_roots;
public:
JfrStartFlightRecordingDCmd(outputStream* output, bool heap);
static const char* name() {
return "JFR.start";
}
@ -109,17 +62,59 @@ class JfrStartFlightRecordingDCmd : public DCmdWithParser {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
static int num_arguments();
virtual void execute(DCmdSource source, TRAPS);
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdStart";
}
};
class JfrStopFlightRecordingDCmd : public DCmdWithParser {
protected:
DCmdArgument<char*> _name;
DCmdArgument<char*> _filename;
class JfrDumpFlightRecordingDCmd : public JfrDCmd {
public:
JfrStopFlightRecordingDCmd(outputStream* output, bool heap);
JfrDumpFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap) {}
static const char* name() {
return "JFR.dump";
}
static const char* description() {
return "Copies contents of a JFR recording to file. Either the name or the recording id must be specified.";
}
static const char* impact() {
return "Low";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdDump";
}
};
class JfrCheckFlightRecordingDCmd : public JfrDCmd {
public:
JfrCheckFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap) {}
static const char* name() {
return "JFR.check";
}
static const char* description() {
return "Checks running JFR recording(s)";
}
static const char* impact() {
return "Low";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdCheck";
}
};
class JfrStopFlightRecordingDCmd : public JfrDCmd {
public:
JfrStopFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap) {}
static const char* name() {
return "JFR.stop";
}
@ -133,12 +128,11 @@ class JfrStopFlightRecordingDCmd : public DCmdWithParser {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
static int num_arguments();
virtual void execute(DCmdSource source, TRAPS);
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdStop";
}
};
class JfrRuntimeOptions;
class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
friend class JfrOptionSet;
protected:
@ -173,8 +167,10 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
}
static int num_arguments();
virtual void execute(DCmdSource source, TRAPS);
virtual void print_help(const char* name) const;
};
bool register_jfr_dcmds();
#endif // SHARE_JFR_DCMD_JFRDCMDS_HPP

View File

@ -487,28 +487,30 @@ Klass* JfrJavaSupport::klass(const jobject handle) {
return obj->klass();
}
// caller needs ResourceMark
const char* JfrJavaSupport::c_str(oop string, JavaThread* t) {
static char* allocate_string(bool c_heap, int length, JavaThread* jt) {
return c_heap ? NEW_C_HEAP_ARRAY(char, length, mtTracing) :
NEW_RESOURCE_ARRAY_IN_THREAD(jt, char, length);
}
const char* JfrJavaSupport::c_str(oop string, JavaThread* t, bool c_heap /* false */) {
DEBUG_ONLY(check_java_thread_in_vm(t));
char* resource_copy = NULL;
char* str = NULL;
const typeArrayOop value = java_lang_String::value(string);
if (value != NULL) {
const int length = java_lang_String::utf8_length(string, value);
resource_copy = NEW_RESOURCE_ARRAY_IN_THREAD(t, char, (length + 1));
if (resource_copy == NULL) {
JfrJavaSupport::throw_out_of_memory_error("Unable to allocate thread local native memory", t);
str = allocate_string(c_heap, length + 1, t);
if (str == NULL) {
JfrJavaSupport::throw_out_of_memory_error("Unable to allocate native memory", t);
return NULL;
}
assert(resource_copy != NULL, "invariant");
java_lang_String::as_utf8_string(string, value, resource_copy, length + 1);
java_lang_String::as_utf8_string(string, value, str, length + 1);
}
return resource_copy;
return str;
}
// caller needs ResourceMark
const char* JfrJavaSupport::c_str(jstring string, JavaThread* t) {
const char* JfrJavaSupport::c_str(jstring string, JavaThread* t, bool c_heap /* false */) {
DEBUG_ONLY(check_java_thread_in_vm(t));
return string != NULL ? c_str(resolve_non_null(string), t) : NULL;
return string != NULL ? c_str(resolve_non_null(string), t, c_heap) : NULL;
}
/*

View File

@ -75,9 +75,8 @@ class JfrJavaSupport : public AllStatic {
// misc
static Klass* klass(const jobject handle);
// caller needs ResourceMark
static const char* c_str(jstring string, JavaThread* jt);
static const char* c_str(oop string, JavaThread* t);
static const char* c_str(jstring string, JavaThread* jt, bool c_heap = false);
static const char* c_str(oop string, JavaThread* jt, bool c_heap = false);
// exceptions
static void throw_illegal_state_exception(const char* message, TRAPS);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2021, 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
@ -58,7 +58,8 @@
JFR_LOG_TAG(jfr, metadata) \
JFR_LOG_TAG(jfr, event) \
JFR_LOG_TAG(jfr, setting) \
JFR_LOG_TAG(jfr, dcmd)
JFR_LOG_TAG(jfr, dcmd) \
JFR_LOG_TAG(jfr, start)
/* NEW TAGS, DONT FORGET TO UPDATE JAVA SIDE */
#endif // SHARE_JFR_UTILITIES_JFRLOGTAGSETS_HPP

View File

@ -1757,6 +1757,22 @@ is needed.
.PP
You can specify values for multiple parameters by separating them with a
comma.
.PP
Event settings and .jfc options can also be specified using the following
syntax:
.TP
.B \f[CB]option=\f[R]\f[I]value\f[R]
Specifies the option value to modify. To list available options, use the
JAVA_HOME/bin/jfr tool.
.TP
.B \f[CB]event-setting=\f[R]\f[I]value\f[R]
Specifies the event setting value to modify. Use the form:
<event-name>#<setting-name>=<value>
To add a new event setting, prefix the event name with '+'.
.PP
In case of a conflict between a parameter and a .jfc option, the parameter
will take precedence. The whitespace delimiter can be omitted for timespan values, i.e. 20ms. For
more information about the settings syntax, see Javadoc of the jdk.jfr package.
.RE
.TP
.B \f[CB]\-XX:ThreadStackSize=\f[R]\f[I]size\f[R]

View File

@ -562,6 +562,22 @@ is needed.
Use \f[CB]none\f[R] to start a recording without a predefined
configuration file.
(STRING, \f[CB]JAVA\-HOME\f[R]/lib/jfr/default.jfc)
.PP
Event settings and .jfc options can also be specified using the following
syntax:
.TP
.B \f[CB]option=\f[R]\f[I]value\f[R]
Specifies the option value to modify. To list available options, use the
JAVA_HOME/bin/jfr tool.
.TP
.B \f[CB]event-setting=\f[R]\f[I]value\f[R]
Specifies the event setting value to modify. Use the form:
<event-name>#<setting-name>=<value>
To add a new event setting, prefix the event name with '+'.
.PP
In case of a conflict between a parameter and a .jfc option, the parameter
will take precedence. The whitespace delimiter can be omitted for timespan values, i.e. 20ms. For
more information about the settings syntax, see Javadoc of the jdk.jfr package.
.RE
.TP
.B \f[CB]JFR.stop\f[R] [\f[I]options\f[R]]

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2021, 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
@ -85,7 +85,11 @@ public enum LogTag {
/**
* Covers usage of jcmd with JFR
*/
JFR_DCMD(12);
JFR_DCMD(12),
/**
* -XX:StartFlightRecording
*/
JFR_START(13);
/* set from native side */
volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, 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
@ -38,6 +38,9 @@ import java.util.List;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.Utils;
@ -50,15 +53,62 @@ abstract class AbstractDCmd {
private final StringBuilder currentLine = new StringBuilder(80);
private final List<String> lines = new ArrayList<>();
private String source;
// Called by native
public abstract String[] printHelp();
// Called by native
public abstract Argument[] getArgumentInfos();
// Called by native
protected abstract void execute(ArgumentParser parser) throws DCmdException;
// Called by native
public final String[] execute(String source, String arg, char delimiter) throws DCmdException {
this.source = source;
try {
boolean log = Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG);
if (log) {
System.out.println(arg);
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing " + this.getClass().getSimpleName() + ": " + arg);
}
ArgumentParser parser = new ArgumentParser(getArgumentInfos(), arg, delimiter);
parser.parse();
if (log) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "DCMD options: " + parser.getOptions());
if (parser.hasExtendedOptions()) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "JFC options: " + parser.getExtendedOptions());
}
}
execute(parser);
return getResult();
}
catch (IllegalArgumentException iae) {
DCmdException e = new DCmdException(iae.getMessage());
e.addSuppressed(iae);
throw e;
}
}
protected final FlightRecorder getFlightRecorder() {
return FlightRecorder.getFlightRecorder();
}
public final String[] getResult() {
protected final String[] getResult() {
return lines.toArray(new String[lines.size()]);
}
protected void logWarning(String message) {
if (source.equals("internal")) { // -XX:StartFlightRecording
Logger.log(LogTag.JFR_START, LogLevel.WARN, message);
} else { // DiagnosticMXBean or JCMD
println("Warning! " + message);
}
}
public String getPid() {
// Invoking ProcessHandle.current().pid() would require loading more
// classes during startup so instead JVM.getJVM().getPid() is used.
@ -192,4 +242,28 @@ abstract class AbstractDCmd {
}
throw new DCmdException("Could not find %s.\n\nUse JFR.check without options to see list of all available recordings.", name);
}
protected final String exampleRepository() {
if ("\r\n".equals(System.lineSeparator())) {
return "C:\\Repositories";
} else {
return "/Repositories";
}
}
protected final String exampleFilename() {
if ("\r\n".equals(System.lineSeparator())) {
return "C:\\Users\\user\\recording.jfr";
} else {
return "/recordings/recording.jfr";
}
}
protected final String exampleDirectory() {
if ("\r\n".equals(System.lineSeparator())) {
return "C:\\Directory\\recordings";
} else {
return "/directory/recordings";
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2021, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.internal.dcmd;
record Argument(
String name,
String description,
String type,
boolean mandatory,
String defaultValue,
boolean allowMultiple
) { }

View File

@ -0,0 +1,337 @@
/*
* Copyright (c) 2021, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.internal.dcmd;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class ArgumentParser {
private final Map<String, Object> options = new HashMap<>();
private final Map<String, Object> extendedOptions = new HashMap<>();
private final StringBuilder builder = new StringBuilder();
private final String text;
private final char delimiter;
private final String keyValueDelimiter;
private final String valueDelimiter;
private final Argument[] arguments;
private int position;
ArgumentParser(Argument[] arguments, String text, char delimiter) {
this.text = text;
this.delimiter = delimiter;
this.arguments = arguments;
this.keyValueDelimiter = "=" + delimiter;
this.valueDelimiter = Character.toString(delimiter);
}
public Map<String, Object> parse() {
eatDelimiter();
while (!atEnd()) {
String key = readText(keyValueDelimiter);
String value = null;
if (accept('=')) {
value = readText(valueDelimiter);
}
if (!atEnd() && !accept(delimiter)) { // must be followed by delimiter
throw new IllegalArgumentException("Expected delimiter, but found " + currentChar());
}
addOption(key, value);
eatDelimiter();
}
checkMandatory();
return options;
}
private void checkMandatory() {
for (Argument arg : arguments) {
if (!options.containsKey(arg.name())) {
if (arg.mandatory()) {
throw new IllegalArgumentException("The argument '" + arg.name() + "' is mandatory");
}
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void addOption(String key, String value) {
boolean found = false;
for (Argument arg : arguments) {
if (arg.name().equals(key)) {
found = true;
Object v = value(key, arg.type(), value);
if (arg.allowMultiple()) {
var list = (List<Object>) options.computeIfAbsent(key, x -> new ArrayList<>());
if (v instanceof List l) {
list.addAll(l);
} else {
list.add(v);
}
} else {
if (options.containsKey(key)) {
throw new IllegalArgumentException("Duplicates in diagnostic command arguments");
}
options.put(key, v);
}
}
}
if (!found) {
extendedOptions.put(key, value);
}
}
private char currentChar() {
return text.charAt(position);
}
private char lastChar() {
return text.charAt(position -1);
}
private boolean atEnd() {
return !(position < text.length());
}
private void eatDelimiter() {
while (!atEnd() && currentChar() == delimiter) {
position++;
}
}
private boolean accept(char c) {
if (!atEnd() && currentChar() == c) {
position++;
return true;
}
return false;
}
// Mostly copied from native DCmdParser
private String readText(String abortChars) {
builder.setLength(0);
boolean quoted = false; ;
while (position <= text.length() - 1 && abortChars.indexOf(currentChar()) == -1) {
if (currentChar() == '\"' || currentChar() == '\'') {
char quote =currentChar();
quoted = true;
while (position < text.length() - 1) {
position++;
if (currentChar() == quote && lastChar() != '\\') {
break;
}
builder.append(currentChar());
}
if (currentChar() != quote) {
throw new IllegalArgumentException("Format error in diagnostic command arguments");
}
break;
}
builder.append(currentChar());
position++;
}
if (quoted) {
position++;
}
return builder.toString();
}
private Object value(String name, String type, String text) {
return switch (type) {
case "STRING", "STRING SET" -> text == null ? "" : text;
case "BOOLEAN" -> parseBoolean(name, text);
case "NANOTIME" -> parseNanotime(name, text);
case "MEMORY SIZE" -> parseMemorySize(name, text);
default -> throw new InternalError("Unknown type: " + type);
};
}
private Boolean parseBoolean(String name, String text) {
if ("true".equals(text)) {
return Boolean.TRUE;
}
if ("false".equals(text)) {
return Boolean.FALSE;
}
String msg = "Boolean parsing error in command argument '" + name + "'. Could not parse: " + text + ".";
throw new IllegalArgumentException(msg);
}
private Object parseMemorySize(String name, String text) {
if (text == null) {
throw new IllegalArgumentException("Parsing error memory size value: syntax error, value is null");
}
int index = indexOfUnit(text);
String textValue = text.substring(0, index);
String unit = text.substring(index);
long bytes;
try {
bytes = Long.parseLong(textValue);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Parsing error memory size value: invalid value");
}
if (bytes < 0) {
throw new IllegalArgumentException("Parsing error memory size value: negative values not allowed");
}
if (unit.isEmpty()) {
return bytes;
}
return switch(unit.toLowerCase()) {
case "k", "kb" -> bytes * 1024;
case "m", "mb"-> bytes * 1024 * 1024;
case "g", "gb" -> bytes * 1024 * 1024 * 1024;
default -> throw new IllegalArgumentException("Parsing error memory size value: invalid value");
};
}
private Object parseNanotime(String name, String text) {
if (text == null) {
throw new IllegalArgumentException("Integer parsing error nanotime value: syntax error, value is null");
}
int index = indexOfUnit(text);
String textValue = text.substring(0, index);
String unit = text.substring(index);
long time;
try {
time = Long.parseLong(textValue);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Integer parsing error nanotime value: syntax error");
}
if (unit.isEmpty()) {
if (time == 0) {
return Long.valueOf(0);
}
throw new IllegalArgumentException("Integer parsing error nanotime value: unit required");
}
return switch(unit) {
case "ns" -> time;
case "us" -> time * 1000;
case "ms" -> time * 1000 * 1000;
case "s" -> time * 1000 * 1000 * 1000;
case "m" -> time * 60 * 1000 * 1000 * 1000;
case "h" -> time * 60 * 60* 1000 * 1000 * 1000;
case "d" -> time * 24 * 60 * 60 * 1000 * 1000 * 1000;
default -> throw new IllegalArgumentException("Integer parsing error nanotime value: illegal unit");
};
}
int indexOfUnit(String text) {
for (int i = 0; i< text.length(); i++) {
char c = text.charAt(i);
if (i == 0 && c == '-') { // Accept negative values.
continue;
}
if (!Character.isDigit(c)) {
return i;
}
}
return text.length();
}
@SuppressWarnings("unchecked")
<T> T getOption(String name) {
return (T) options.get(name);
}
Map<String, Object> getOptions() {
return options;
}
void checkUnknownArguments() {
if (!extendedOptions.isEmpty()) {
String name = extendedOptions.keySet().iterator().next();
throw new IllegalArgumentException("Unknown argument '" + name + "' in diagnostic command.");
}
}
Map<String, Object> getExtendedOptions() {
return extendedOptions;
}
boolean hasExtendedOptions() {
return !extendedOptions.isEmpty();
}
void checkSpelling(Set<String> excludeSet) {
for (String name : extendedOptions.keySet()) {
if (!excludeSet.contains(name)) { // ignore names specified in .jfc
checkSpellingError(name);
}
}
}
private void checkSpellingError(String name) {
for (Argument a : arguments) {
String expected = a.name();
String s = name.toLowerCase();
int lengthDifference = expected.length() - s.length();
boolean spellingError = false;
if (lengthDifference == 0) {
if (expected.equals(s)) {
spellingError = true; // incorrect case, or we wouldn't be here
} else {
if (s.length() < 6) {
spellingError = diff(expected, s) < 2; // one incorrect letter
} else {
spellingError = diff(expected, s) < 3; // two incorrect letter
}
}
}
if (lengthDifference == 1) {
spellingError = inSequence(expected, s); // missing letter
}
if (lengthDifference == -1) {
spellingError = inSequence(s, expected); // additional letter
}
if (spellingError) {
throw new IllegalArgumentException("Error! Did you mean '" + expected + "' instead of '" + name + "'?");
}
}
}
private int diff(String a, String b) {
int count = a.length();
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) == b.charAt(i)) {
count--;
}
}
return count;
}
private boolean inSequence(String longer, String shorter) {
int l = 0;
int s = 0;
while (l < longer.length() && s < shorter.length()) {
if (longer.charAt(l) == shorter.charAt(s)) {
s++;
}
l++;
}
return shorter.length() == s; // if 0, all letters in longer found in shorter
}
}

View File

@ -36,9 +36,6 @@ import java.util.StringJoiner;
import jdk.jfr.EventType;
import jdk.jfr.Recording;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.Utils;
/**
@ -46,27 +43,12 @@ import jdk.jfr.internal.Utils;
*
*/
final class DCmdCheck extends AbstractDCmd {
/**
* Execute JFR.check
*
* @param recordingText name or id of the recording to check, or
* {@code null} to show a list of all recordings.
*
* @param verbose if event settings should be included.
*
* @return result output
*
* @throws DCmdException if the check could not be completed.
*/
public String[] execute(String recordingText, Boolean verbose) throws DCmdException {
executeInternal(recordingText, verbose);
return getResult();
}
private void executeInternal(String name, Boolean verbose) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdCheck: name=" + name + ", verbose=" + verbose);
}
@Override
protected void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
Boolean verbose = parser.getOption("verbose");
String name = parser.getOption("name");
if (verbose == null) {
verbose = Boolean.FALSE;
@ -161,4 +143,42 @@ final class DCmdCheck extends AbstractDCmd {
});
return sorted;
}
@Override
public String[] printHelp() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Syntax : JFR.check [options]
Options:
name (Optional) Name of the flight recording. (STRING, no default value)
verbose (Optional) Flag for printing the event settings for the recording
(BOOLEAN, false)
Options must be specified using the <key> or <key>=<value> syntax.
Example usage:
$ jcmd <pid> JFR.check
$ jcmd <pid> JFR.check verbose=true
$ jcmd <pid> JFR.check name=1
$ jcmd <pid> JFR.check name=benchmark
$ jcmd <pid> JFR.check name=2 verbose=true
""".lines().toArray(String[]::new);
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("name",
"Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings",
"STRING", false, null, false),
new Argument("verbose",
"Print event settings for the recording(s)","BOOLEAN",
false, "false", false)
};
}
}

View File

@ -241,4 +241,19 @@ final class DCmdConfigure extends AbstractDCmd {
printBytes(Options.getMaxChunkSize());
println();
}
@Override
public String[] printHelp() {
throw new InternalError("Should not reach here!");
}
@Override
public Argument[] getArgumentInfos() {
throw new InternalError("Should not reach here!");
}
@Override
protected void execute(ArgumentParser parser) throws DCmdException {
throw new InternalError("Should not reach here!");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, 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
@ -37,9 +37,6 @@ import java.time.format.DateTimeParseException;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
@ -53,33 +50,17 @@ import jdk.jfr.internal.WriteableUserPath;
*/
// Instantiated by native
final class DCmdDump extends AbstractDCmd {
/**
* Execute JFR.dump.
*
* @param name name or id of the recording to dump, or {@code null} to dump everything
*
* @param filename file path where recording should be written, not null
* @param maxAge how far back in time to dump, may be null
* @param maxSize how far back in size to dump data from, may be null
* @param begin point in time to dump data from, may be null
* @param end point in time to dump data to, may be null
* @param pathToGcRoots if Java heap should be swept for reference chains
*
* @return result output
*
* @throws DCmdException if the dump could not be completed
*/
public String[] execute(String name, String filename, Long maxAge, Long maxSize, String begin, String end, Boolean pathToGcRoots) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG,
"Executing DCmdDump: name=" + name +
", filename=" + filename +
", maxage=" + maxAge +
", maxsize=" + maxSize +
", begin=" + begin +
", end=" + end +
", path-to-gc-roots=" + pathToGcRoots);
}
@Override
public void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
String name = parser.getOption("name");
String filename = parser.getOption("filename");
Long maxAge = parser.getOption("maxage");
Long maxSize = parser.getOption("maxsize");
String begin = parser.getOption("begin");
String end = parser.getOption("end");
Boolean pathToGcRoots = parser.getOption("path-to-gc-roots");
if (FlightRecorder.getFlightRecorder().getRecordings().isEmpty()) {
throw new DCmdException("No recordings to dump from. Use JFR.start to start a recording.");
@ -134,7 +115,6 @@ final class DCmdDump extends AbstractDCmd {
} catch (IOException | InvalidPathException e) {
throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage());
}
return getResult();
}
public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException, IOException {
@ -213,4 +193,109 @@ final class DCmdDump extends AbstractDCmd {
return pr.newSnapshotClone("Dumped by user", pathToGcRoots);
}
@Override
public String[] printHelp() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Syntax : JFR.dump [options]
Options:
begin (Optional) Specify the time from which recording data will be included
in the dump file. The format is specified as local time.
(STRING, no default value)
end (Optional) Specify the time to which recording data will be included
in the dump file. The format is specified as local time.
(STRING, no default value)
Note: For both begin and end, the time must be in a format that can
be read by any of these methods:
java.time.LocalTime::parse(String),
java.time.LocalDateTime::parse(String)
java.time.Instant::parse(String)
For example, "13:20:15", "2020-03-17T09:00:00" or
"2020-03-17T09:00:00Z".
Note: begin and end times correspond to the timestamps found within
the recorded information in the flight recording data.
Another option is to use a time relative to the current time that is
specified by a negative integer followed by "s", "m" or "h".
For example, "-12h", "-15m" or "-30s"
filename (Optional) Name of the file to which the flight recording data is
dumped. If no filename is given, a filename is generated from the PID
and the current date. The filename may also be a directory in which
case, the filename is generated from the PID and the current date in
the specified directory. (STRING, no default value)
maxage (Optional) Length of time for dumping the flight recording data to a
file. (INTEGER followed by 's' for seconds 'm' for minutes or 'h' for
hours, no default value)
maxsize (Optional) Maximum size for the amount of data to dump from a flight
recording in bytes if one of the following suffixes is not used:
'm' or 'M' for megabytes OR 'g' or 'G' for gigabytes.
(STRING, no default value)
name (Optional) Name of the recording. If no name is given, data from all
recordings is dumped. (STRING, no default value)
path-to-gc-root (Optional) Flag for saving the path to garbage collection (GC) roots
at the time the recording data is dumped. The path information is
useful for finding memory leaks but collecting it can cause the
application to pause for a short period of time. Turn on this flag
only when you have an application that you suspect has a memory
leak. (BOOLEAN, false)
Options must be specified using the <key> or <key>=<value> syntax.
Example usage:
$ jcmd <pid> JFR.dump
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.dump filename=%s
$ jcmd <pid> JFR.dump name=1 filename=%s
$ jcmd <pid> JFR.dump maxage=1h
$ jcmd <pid> JFR.dump maxage=1h maxsize=50M
$ jcmd <pid> JFR.dump fillename=leaks.jfr path-to-gc-root=true
$ jcmd <pid> JFR.dump begin=13:15
$ jcmd <pid> JFR.dump begin=13:15 end=21:30:00
$ jcmd <pid> JFR.dump end=18:00 maxage=10m
$ jcmd <pid> JFR.dump begin=2021-09-15T09:00:00 end=2021-09-15T10:00:00
$ jcmd <pid> JFR.dump begin=-1h
$ jcmd <pid> JFR.dump begin=-15m end=-5m
""".formatted(exampleDirectory(), exampleFilename()).lines().toArray(String[]::new);
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("name",
"Recording name, e.g. \\\"My Recording\\\"",
"STRING", false, null, false),
new Argument("filename",
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, null, false),
new Argument("maxage",
"Maximum duration to dump, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, null, false),
new Argument("maxsize", "Maximum amount of bytes to dump, in (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
new Argument("begin",
"Point in time to dump data from, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d",
"STRING", false, null, false),
new Argument("end",
"Point in time to dump data to, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d",
"STRING", false, null, false),
new Argument("path-to-gc-roots",
"Collect path to GC roots",
"BOOLEAN", false, "false", false)
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, 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
@ -32,22 +32,24 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.OldObjectSample;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.jfc.JFC;
import jdk.jfr.internal.jfc.model.JFCModel;
import jdk.jfr.internal.jfc.model.XmlInput;
/**
* JFR.start
@ -56,46 +58,31 @@ import jdk.jfr.internal.jfc.JFC;
//Instantiated by native
final class DCmdStart extends AbstractDCmd {
/**
* Execute JFR.start.
*
* @param name optional name that can be used to identify recording.
* @param settings names of settings files to use, i.e. "default" or
* "default.jfc".
* @param delay delay before recording is started, in nanoseconds. Must be
* at least 1 second.
* @param duration duration of the recording, in nanoseconds. Must be at
* least 1 second.
* @param disk if recording should be persisted to disk
* @param path file path where recording data should be written
* @param maxAge how long recording data should be kept in the disk
* repository, or {@code 0} if no limit should be set.
*
* @param maxSize the minimum amount data to keep in the disk repository
* before it is discarded, or {@code 0} if no limit should be
* set.
*
* @param dumpOnExit if recording should dump on exit
*
* @return result output
*
* @throws DCmdException if recording could not be started
*/
@SuppressWarnings("resource")
public String[] execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Long flush, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name +
", settings=" + Arrays.asList(settings) +
", delay=" + delay +
", duration=" + duration +
", disk=" + disk+
", filename=" + path +
", maxage=" + maxAge +
", flush-interval=" + flush +
", maxsize=" + maxSize +
", dumponexit=" + dumpOnExit +
", path-to-gc-roots=" + pathToGcRoots);
private Object source;
@Override
public void execute(ArgumentParser parser) throws DCmdException {
String name = parser.getOption("name");
List<String> list = parser.getOption("settings");
String[] settings = null;
if (list == null) {
settings = new String[] {"default.jfc"};
} else {
settings = list.toArray(new String[0]);
}
if (settings.length == 1 && "none".equals(settings[0])) {
settings = new String[0];
}
Long delay = parser.getOption("delay");
Long duration = parser.getOption("duration");
Boolean disk = parser.getOption("disk");
String path = parser.getOption("filename");
Long maxAge = parser.getOption("maxage");
Long maxSize = parser.getOption("maxsize");
Long flush = parser.getOption("flush-interval");
Boolean dumpOnExit = parser.getOption("dumponexit");
Boolean pathToGcRoots = parser.getOption("path-to-gc-roots");
if (name != null) {
try {
Integer.parseInt(name);
@ -111,15 +98,12 @@ final class DCmdStart extends AbstractDCmd {
if (settings.length == 1 && settings[0].length() == 0) {
throw new DCmdException("No settings specified. Use settings=none to start without any settings");
}
Map<String, String> s = new HashMap<>();
for (String configName : settings) {
try {
s.putAll(JFC.createKnown(configName).getSettings());
} catch(FileNotFoundException e) {
throw new DCmdException("Could not find settings file'" + configName + "'", e);
} catch (IOException | ParseException e) {
throw new DCmdException("Could not parse settings file '" + settings[0] + "'", e);
}
LinkedHashMap<String, String> s;
if (parser.hasExtendedOptions()) {
s = configureExtended(settings, parser);
} else {
s = configureStandard(settings);
}
OldObjectSample.updateSettingPathToGcRoots(s, pathToGcRoots);
@ -236,10 +220,58 @@ final class DCmdStart extends AbstractDCmd {
print("Use jcmd " + getPid() + " JFR." + cmd + " " + recordingspecifier + " " + fileOption + "to copy recording data to file.");
println();
}
return getResult();
}
private LinkedHashMap<String, String> configureStandard(String[] settings) throws DCmdException {
LinkedHashMap<String, String> s = new LinkedHashMap<>();
for (String configName : settings) {
try {
s.putAll(JFC.createKnown(configName).getSettings());
} catch(FileNotFoundException e) {
throw new DCmdException("Could not find settings file'" + configName + "'", e);
} catch (IOException | ParseException e) {
throw new DCmdException("Could not parse settings file '" + settings[0] + "'", e);
}
}
return s;
}
private LinkedHashMap<String, String> configureExtended(String[] settings, ArgumentParser parser) throws DCmdException {
List<SafePath> paths = new ArrayList<>();
for (String setting : settings) {
paths.add(JFC.createSafePath(setting));
}
try {
JFCModel model = new JFCModel(paths);
Set<String> jfcOptions = new HashSet<>();
for (XmlInput input : model.getInputs()) {
jfcOptions.add(input.getName());
}
parser.checkSpelling(jfcOptions);
Map<String, String> jfcSettings = model.getSettings();
for (var entry : parser.getExtendedOptions().entrySet()) {
String value = (String)entry.getValue();
String optionName = entry.getKey();
boolean added = optionName.startsWith("+");
if (!added && !jfcOptions.contains(optionName) && !jfcSettings.containsKey(optionName)) {
// Option/setting doesn't exist and it is not a spelling error.
// By not throwing an exception, and instead print a warning,
// it is easier migrate to a newer JDK version without
// breaking startup scripts
logWarning("The .jfc option/setting '" + optionName + "' doesn't exist.");
} else {
model.configure(entry.getKey(), value);
}
}
return model.getSettings();
} catch (IllegalArgumentException iae) {
throw new DCmdException(iae.getMessage()); // spelling error, invalid value
} catch (FileNotFoundException ioe) {
throw new DCmdException("Could not find settings file'" + settings[0] + "'", ioe);
} catch (IOException | ParseException e) {
throw new DCmdException("Could not parse settings file '" + settings[0] + "'", e);
}
}
// Instruments JDK-events on class load to reduce startup time
private void initializeWithForcedInstrumentation(Map<String, String> settings) {
@ -271,4 +303,154 @@ final class DCmdStart extends AbstractDCmd {
}
return false;
}
@Override
public String[] printHelp() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Syntax : JFR.start [options]
Options:
delay (Optional) Length of time to wait before starting to record
(INTEGER followed by 's' for seconds 'm' for minutes or h' for
hours, 0s)
disk (Optional) Flag for also writing the data to disk while recording
(BOOLEAN, true)
dumponexit (Optional) Flag for writing the recording to disk when the Java
Virtual Machine (JVM) shuts down. If set to 'true' and no value
is given for filename, the recording is written to a file in the
directory where the process was started. The file name is a
system-generated name that contains the process ID, the recording
ID and the current time stamp. (For example:
id-1-2021_09_14_09_00.jfr) (BOOLEAN, false)
duration (Optional) Length of time to record. Note that 0s means forever
(INTEGER followed by 's' for seconds 'm' for minutes or 'h' for
hours, 0s)
filename (Optional) Name of the file to which the flight recording data is
written when the recording is stopped. If no filename is given, a
filename is generated from the PID and the current date and is
placed in the directory where the process was started. The
filename may also be a directory in which case, the filename is
generated from the PID and the current date in the specified
directory. (STRING, no default value)
maxage (Optional) Maximum time to keep the recorded data on disk. This
parameter is valid only when the disk parameter is set to true.
Note 0s means forever. (INTEGER followed by 's' for seconds 'm'
for minutes or 'h' for hours, 0s)
maxsize (Optional) Maximum size of the data to keep on disk in bytes if
one of the following suffixes is not used: 'm' or 'M' for
megabytes OR 'g' or 'G' for gigabytes. This parameter is valid
only when the disk parameter is set to 'true'. The value must not
be less than the value for the maxchunksize parameter set with
the JFR.configure command. (STRING, 0 (no max size))
name (Optional) Name of the recording. If no name is provided, a name
is generated. Make note of the generated name that is shown in
the response to the command so that you can use it with other
commands. (STRING, system-generated default name)
path-to-gc-root (Optional) Flag for saving the path to garbage collection (GC)
roots at the end of a recording. The path information is useful
for finding memory leaks but collecting it is time consuming.
Turn on this flag only when you have an application that you
suspect has a memory leak. If the settings parameter is set to
'profile', then the information collected includes the stack
trace from where the potential leaking object wasallocated.
(BOOLEAN, false)
settings (Optional) Name of the settings file that identifies which events
to record. To specify more than one file, use the settings
parameter repeatedly. Include the path if the file is not in
JAVA-HOME/lib/jfr. The following profiles are included with the
JDK in the JAVA-HOME/lib/jfr directory: 'default.jfc': collects a
predefined set of information with low overhead, so it has minimal
impact on performance and can be used with recordings that run
continuously; 'profile.jfc': Provides more data than the
'default.jfc' profile, but with more overhead and impact on
performance. Use this configuration for short periods of time
when more information is needed. Use none to start a recording
without a predefined configuration file. (STRING,
JAVA-HOME/lib/jfr/default.jfc)
Event settings and .jfc options can also be specified using the following syntax:
jfc-option=value (Optional) The option value to modify. To see available
options for a .jfc file, use the 'jfr configure' command.
event-setting=value (Optional) The event setting value to modify. Use the form:
<event-name>#<setting-name>=<value>
To add a new event setting, prefix the event name with '+'.
In case of a conflict between a parameter and a .jfc option, the parameter will
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.
Options must be specified using the <key> or <key>=<value> syntax.
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
Note, if the default event settings are modified, overhead may exceed 1%%.
""".formatted(exampleDirectory()).lines().toArray(String[]::new);
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("name",
"Name that can be used to identify recording, e.g. \\\"My Recording\\\"",
"STRING", false, null, false),
new Argument("settings",
"Settings file(s), e.g. profile or default. See JAVA_HOME/lib/jfr",
"STRING SET", false, "deafult.jfc", true),
new Argument("delay",
"Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h.",
"NANOTIME", false, "0s", false),
new Argument("duration",
"Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s.",
"NANOTIME", false, null, false),
new Argument("disk",
"Recording should be persisted to disk",
"BOOLEAN", false, "true", false),
new Argument("filename",
"Resulting recording filename, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
new Argument("maxage",
"Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, "0", false),
new Argument("maxsize",
"Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, "250M", false),
new Argument("flush-interval",
"Dump running recording when JVM shuts down",
"NANOTIME", false, "1s", false),
new Argument("dumponexit",
"Minimum time before flushing buffers, measured in (s)econds, e.g. 4 s, or 0 for flushing when a recording ends",
"BOOLEAN", false, "false", false),
new Argument("path-to-gc-roots",
"Collect path to GC roots",
"BOOLEAN", false, "false", false)
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, 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
@ -29,9 +29,6 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import jdk.jfr.Recording;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.SecuritySupport.SafePath;
/**
@ -41,25 +38,11 @@ import jdk.jfr.internal.SecuritySupport.SafePath;
// Instantiated by native
final class DCmdStop extends AbstractDCmd {
/**
* Execute JFR.stop
*
* Requires that either {@code name} or {@code id} is set.
*
* @param name name or id of the recording to stop.
*
* @param filename file path where data should be written after recording has
* been stopped, or {@code null} if recording shouldn't be written
* to disk.
* @return result text
*
* @throws DCmdException if recording could not be stopped
*/
public String[] execute(String name, String filename) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + ", filename=" + filename);
}
@Override
protected void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
String name = parser.getOption("name");
String filename = parser.getOption("filename");
try {
SafePath safePath = null;
Recording recording = findRecording(name);
@ -76,7 +59,6 @@ final class DCmdStop extends AbstractDCmd {
recording.stop();
reportOperationComplete("Stopped", recording.getName(), safePath);
recording.close();
return getResult();
} catch (InvalidPathException | DCmdException e) {
if (filename != null) {
throw new DCmdException("Could not write recording \"%s\" to file. %s", name, e.getMessage());
@ -84,4 +66,42 @@ final class DCmdStop extends AbstractDCmd {
throw new DCmdException(e, "Could not stop recording \"%s\".", name, e.getMessage());
}
}
@Override
public String[] printHelp() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Syntax : JFR.stop [options]
Options:
filename (Optional) Name of the file to which the recording is written when the
recording is stopped. If no path is provided, the data from the recording
is discarded. (STRING, no default value)
name Name of the recording (STRING, no default value)
Options must be specified using the <key> or <key>=<value> syntax.
Example usage:
$ jcmd <pid> JFR.stop name=1
$ jcmd <pid> JFR.stop name=benchmark filename=%s
$ jcmd <pid> JFR.stop name=5 filename=recording.jfr
""".formatted(exampleDirectory()).
lines().toArray(String[]::new);
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("name",
"Recording text,.e.g \\\"My Recording\\\"",
"STRING", true, null, false),
new Argument("filename",
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, null, false)
};
}
}

View File

@ -117,6 +117,32 @@ public final class JFC {
return JFCParser.createConfiguration(name, reader);
}
/**
* Create a path to a .jfc file.
* <p>
* If the name is predefined name,
* i.e. "default" or "profile.jfc", it will return the path for
* the predefined path in the JDK.
*
* @param path textual representation of the path
*
* @return a safe path, not null
*/
public static SafePath createSafePath(String path) {
for (SafePath predefined : SecuritySupport.getPredefinedJFCFiles()) {
try {
String name = JFC.nameFromPath(predefined.toPath());
if (name.equals(path) || (name + ".jfc").equals(path)) {
return predefined;
}
} catch (IOException e) {
throw new InternalError("Error in predefined .jfc file", e);
}
}
return new SafePath(path);
}
private static String nullSafeFileName(Path file) throws IOException {
Path filename = file.getFileName();
if (filename == null) {

View File

@ -120,6 +120,16 @@ public final class JFCModel {
return configuration;
}
public LinkedHashMap<String, String> getSettings() {
LinkedHashMap<String, String> result = new LinkedHashMap<>();
for (XmlEvent event : configuration.getEvents()) {
for (XmlSetting setting : event.getSettings()) {
result.put(event.getName() + "#" + setting.getName(), setting.getContent());
}
}
return result;
}
public void saveToFile(SafePath path) throws IOException {
try (PrintWriter p = new PrintWriter(path.toFile(), Charset.forName("UTF-8"))) {
PrettyPrinter pp = new PrettyPrinter(p);

View File

@ -236,24 +236,11 @@ final class Configure extends Command {
private List<SafePath> makeSafePathList(String value) {
List<SafePath> paths = new ArrayList<>();
for (String name : value.split(",")) {
paths.add(makeSafePath(name));
paths.add(JFC.createSafePath(name));
}
return paths;
}
private SafePath makeSafePath(String path) {
for (SafePath predefined : SecuritySupport.getPredefinedJFCFiles()) {
try {
String name = JFC.nameFromPath(predefined.toPath());
if (name.equals(path) || (name + ".jfc").equals(path)) {
return predefined;
}
} catch (IOException e) {
throw new InternalError("Error in predefined .jfc file", e);
}
}
return new SafePath(path);
}
private void ensureInputFiles() throws InternalError {
if (inputFiles.isEmpty()) {

View File

@ -93,11 +93,11 @@ public class TestBadOptionValues {
"duration");
test(START_FLIGHT_RECORDING, "Integer parsing error nanotime value: illegal unit",
"delay=1000mq",
"duration=2000mss",
"maxage=-1000");
"duration=2000mss");
test(START_FLIGHT_RECORDING, "Integer parsing error nanotime value: unit required",
"delay=3037",
"maxage=1");
"maxage=1",
"maxage=-1000");
// Memory size options
test(START_FLIGHT_RECORDING, "Parsing error memory size value: negative values not allowed",
@ -168,11 +168,5 @@ public class TestBadOptionValues {
testBoolean(FLIGHT_RECORDER_OPTIONS,
"samplethreads=falseq",
"retransform=0");
// Not existing options
test(START_FLIGHT_RECORDING, "Unknown argument 'dumponexitt' in diagnostic command.",
"dumponexitt=true");
test(FLIGHT_RECORDER_OPTIONS, "Unknown argument 'notexistoption' in diagnostic command.",
"notexistoption");
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2021, 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 java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
/**
* @test
* @summary Start a recording with custom settings
* @key jfr
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @modules jdk.jfr/jdk.jfr.internal
*
* @run main/othervm -XX:StartFlightRecording:jdk.JVMInformation#enabled=false
* jdk.jfr.startupargs.TestEventSettings knownSetting
*
* @run main/othervm -XX:StartFlightRecording:com.example.Hello#stackTrace=true
* jdk.jfr.startupargs.TestEventSettings unknownSetting
*
* @run main/othervm -XX:StartFlightRecording:+HelloWorld#enabled=true
* jdk.jfr.startupargs.TestEventSettings addedUnknownSetting
*
* @run main/othervm
* -XX:StartFlightRecording:+A.B#enabled=true,+C.D#enabled=false
* jdk.jfr.startupargs.TestEventSettings multipleSettings
*
* @run main/othervm
* -XX:StartFlightRecording:class-loading=true,socket-threshold=100ms
* jdk.jfr.startupargs.TestEventSettings jfcOptions
*/
public class TestEventSettings {
public static void main(String... args) throws Exception {
String subTest = args[0];
System.out.println(subTest);
switch (subTest) {
case "knownSetting" -> assertSetting("jdk.JVMInformation#enabled","false");
case "unknownSetting" -> assertSetting("com.example.Hello#stackTrace", null);
case "addedUnknownSetting" -> assertSetting("HelloWorld#enabled", "true");
case "multipleSettings" -> {
assertSetting("A.B#enabled", "true");
assertSetting("C.D#enabled", "false");
}
case "jfcOptions" -> {
assertSetting("jdk.ClassDefine#enabled","true");
assertSetting("jdk.SocketRead#threshold", "100 ms");
}
default -> throw new Exception("Uknown tes " + subTest);
}
}
private static void assertSetting(String key, String value) throws Exception {
List<Recording> rs = FlightRecorder.getFlightRecorder().getRecordings();
if (rs.isEmpty()) {
throw new Exception("No recording started");
}
if (rs.size() != 1) {
throw new Exception("Expected only one recording");
}
Map<String, String> currentSettings = rs.get(0).getSettings();
String s = currentSettings.get(key);
if (!Objects.equals(s, value)) {
System.out.println("Key:" + key);
System.out.println("Value:" + value);
System.out.println("Result: " + s);
System.out.println("All Setting:");
for (var entry : currentSettings.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
throw new Exception("Expected: " + value + " for " + key + " , got: " + s);
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2021, 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.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
/**
* @test
* @summary Start a recording with custom settings
* @key jfr
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @modules jdk.jfr/jdk.jfr.internal
*
* @run main/othervm jdk.jfr.startupargs.TestJFCWarnings
*/
public class TestJFCWarnings {
public static void main(String... args) throws Exception {
testUnknownOption();
testSpellingError();
}
private static void testSpellingError() throws Exception {
// One character spelling error
launch("-XX:StartFlightRecording:disc=false",
"Did you mean 'disk' instead of 'disc'?");
// One missing character
launch("-XX:StartFlightRecording:setting=my.jfc",
"Did you mean 'settings' instead of 'setting'?");
// One additional character
launch("-XX:StartFlightRecording:disk=false,paths-to-gc-roots=true,name=test",
"Did you mean 'path-to-gc-roots' instead of 'paths-to-gc-roots'?");
// Incorrect case
launch("-XX:StartFlightRecording:fileName=recording.jfr,disk=false",
"Did you mean 'filename' instead of 'fileName'?");
// Two character spelling error in option with more than 6 characters
launch("-XX:StartFlightRecording:name=wrong,filenaim=recording.jfr",
"Did you mean 'filename' instead of 'filenaim'?");
}
private static void testUnknownOption() throws Exception {
// Unknown .jfc option
launch("-XX:StartFlightRecording:zebra=high",
"The .jfc option/setting 'zebra' doesn't exist.");
// Unknown event setting
launch("-XX:StartFlightRecording:com.example.Hello#enabled=true",
"The .jfc option/setting 'com.example.Hello#enabled' doesn't exist.");
}
private static void launch(String commandLine, String expectedOutput) throws Exception {
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(commandLine, "-version");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldContain(expectedOutput);
}
}