8265271: JFR: Allow use of .jfc options when starting JFR
Reviewed-by: mgronlun
This commit is contained in:
parent
f677163b8a
commit
f716711c7b
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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]]
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
) { }
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
100
test/jdk/jdk/jfr/startupargs/TestEventSettings.java
Normal file
100
test/jdk/jdk/jfr/startupargs/TestEventSettings.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
78
test/jdk/jdk/jfr/startupargs/TestJFCWarnings.java
Normal file
78
test/jdk/jdk/jfr/startupargs/TestJFCWarnings.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user