/* * Copyright (c) 2012, 2019, 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. * */ #include "precompiled.hpp" #include "jfr/dcmd/jfrDcmds.hpp" #include "jfr/recorder/access/jfrMemorySizer.hpp" #include "jfr/recorder/access/jfrOptionSet.hpp" #include "jfr/utilities/jfrAllocation.hpp" #include "jfr/utilities/jfrLog.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "runtime/java.hpp" #include "runtime/thread.inline.hpp" #include "services/diagnosticArgument.hpp" #include "services/diagnosticFramework.hpp" #include "utilities/ostream.hpp" struct ObsoleteOption { const char* name; const char* message; }; static const ObsoleteOption OBSOLETE_OPTIONS[] = { {"checkpointbuffersize", ""}, {"maxsize", "Use -XX:StartFlightRecording=maxsize=... instead."}, {"maxage", "Use -XX:StartFlightRecording=maxage=... instead."}, {"settings", "Use -XX:StartFlightRecording=settings=... instead."}, {"defaultrecording", "Use -XX:StartFlightRecording=disk=false to create an in-memory recording."}, {"disk", "Use -XX:StartFlightRecording=disk=... instead."}, {"dumponexit", "Use -XX:StartFlightRecording=dumponexit=... instead."}, {"dumponexitpath", "Use -XX:StartFlightRecording=filename=... instead."}, {"loglevel", "Use -Xlog:jfr=... instead."} }; jlong JfrOptionSet::max_chunk_size() { return _max_chunk_size; } void JfrOptionSet::set_max_chunk_size(jlong value) { _max_chunk_size = value; } jlong JfrOptionSet::global_buffer_size() { return _global_buffer_size; } void JfrOptionSet::set_global_buffer_size(jlong value) { _global_buffer_size = value; } jlong JfrOptionSet::thread_buffer_size() { return _thread_buffer_size; } void JfrOptionSet::set_thread_buffer_size(jlong value) { _thread_buffer_size = value; } jlong JfrOptionSet::memory_size() { return _memory_size; } void JfrOptionSet::set_memory_size(jlong value) { _memory_size = value; } jlong JfrOptionSet::num_global_buffers() { return _num_global_buffers; } void JfrOptionSet::set_num_global_buffers(jlong value) { _num_global_buffers = value; } jint JfrOptionSet::old_object_queue_size() { return (jint)_old_object_queue_size; } void JfrOptionSet::set_old_object_queue_size(jlong value) { _old_object_queue_size = value; } u4 JfrOptionSet::stackdepth() { return _stack_depth; } static const u4 STACK_DEPTH_DEFAULT = 64; static const u4 MIN_STACK_DEPTH = 1; static const u4 MAX_STACK_DEPTH = 2048; void JfrOptionSet::set_stackdepth(u4 depth) { if (depth < MIN_STACK_DEPTH) { _stack_depth = MIN_STACK_DEPTH; } else if (depth > MAX_STACK_DEPTH) { _stack_depth = MAX_STACK_DEPTH; } else { _stack_depth = depth; } } bool JfrOptionSet::sample_threads() { return _sample_threads == JNI_TRUE; } void JfrOptionSet::set_sample_threads(jboolean sample) { _sample_threads = sample; } bool JfrOptionSet::can_retransform() { return _retransform == JNI_TRUE; } void JfrOptionSet::set_retransform(jboolean value) { _retransform = value; } bool JfrOptionSet::sample_protection() { return _sample_protection == JNI_TRUE; } #ifdef ASSERT void JfrOptionSet::set_sample_protection(jboolean protection) { _sample_protection = protection; } #endif bool JfrOptionSet::compressed_integers() { // Set this to false for debugging purposes. return true; } bool JfrOptionSet::allow_retransforms() { #if INCLUDE_JVMTI return true; #else return false; #endif } bool JfrOptionSet::allow_event_retransforms() { return allow_retransforms() && (DumpSharedSpaces || can_retransform()); } // default options for the dcmd parser const char* const default_repository = NULL; const char* const default_global_buffer_size = "512k"; const char* const default_num_global_buffers = "20"; const char* const default_memory_size = "10m"; const char* const default_thread_buffer_size = "8k"; const char* const default_max_chunk_size = "12m"; const char* const default_sample_threads = "true"; const char* const default_stack_depth = "64"; const char* const default_retransform = "true"; const char* const default_old_object_queue_size = "256"; DEBUG_ONLY(const char* const default_sample_protection = "false";) // statics static DCmdArgument _dcmd_repository( "repository", "Flight recorder disk repository location", "STRING", false, default_repository); static DCmdArgument _dcmd_threadbuffersize( "threadbuffersize", "Thread buffer size", "MEMORY SIZE", false, default_thread_buffer_size); static DCmdArgument _dcmd_memorysize( "memorysize", "Size of memory to be used by Flight Recorder", "MEMORY SIZE", false, default_memory_size); static DCmdArgument _dcmd_globalbuffersize( "globalbuffersize", "Global buffer size", "MEMORY SIZE", false, default_global_buffer_size); static DCmdArgument _dcmd_numglobalbuffers( "numglobalbuffers", "Number of global buffers", "JULONG", false, default_num_global_buffers); static DCmdArgument _dcmd_maxchunksize( "maxchunksize", "Maximum size of a single repository disk chunk", "MEMORY SIZE", false, default_max_chunk_size); static DCmdArgument _dcmd_old_object_queue_size ( "old-object-queue-size", "Maximum number of old objects to track", "JINT", false, default_old_object_queue_size); static DCmdArgument _dcmd_sample_threads( "samplethreads", "Thread sampling enable / disable (only sampling when event enabled and sampling enabled)", "BOOLEAN", false, default_sample_threads); #ifdef ASSERT static DCmdArgument _dcmd_sample_protection( "sampleprotection", "Safeguard for stackwalking while sampling threads (false by default)", "BOOLEAN", false, default_sample_protection); #endif static DCmdArgument _dcmd_stackdepth( "stackdepth", "Stack depth for stacktraces (minimum 1, maximum 2048)", "JULONG", false, default_stack_depth); static DCmdArgument _dcmd_retransform( "retransform", "If event classes should be instrumented using JVMTI (by default true)", "BOOLEAN", true, default_retransform); static DCmdParser _parser; static void register_parser_options() { _parser.add_dcmd_option(&_dcmd_repository); _parser.add_dcmd_option(&_dcmd_threadbuffersize); _parser.add_dcmd_option(&_dcmd_memorysize); _parser.add_dcmd_option(&_dcmd_globalbuffersize); _parser.add_dcmd_option(&_dcmd_numglobalbuffers); _parser.add_dcmd_option(&_dcmd_maxchunksize); _parser.add_dcmd_option(&_dcmd_stackdepth); _parser.add_dcmd_option(&_dcmd_sample_threads); _parser.add_dcmd_option(&_dcmd_retransform); _parser.add_dcmd_option(&_dcmd_old_object_queue_size); DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);) } static bool parse_flight_recorder_options_internal(TRAPS) { if (FlightRecorderOptions == NULL) { return true; } const size_t length = strlen((const char*)FlightRecorderOptions); CmdLine cmdline((const char*)FlightRecorderOptions, length, true); _parser.parse(&cmdline, ',', THREAD); if (HAS_PENDING_EXCEPTION) { for (int index = 0; index < 9; index++) { ObsoleteOption option = OBSOLETE_OPTIONS[index]; const char* p = strstr((const char*)FlightRecorderOptions, option.name); const size_t option_length = strlen(option.name); if (p != NULL && p[option_length] == '=') { log_error(arguments) ("-XX:FlightRecorderOptions=%s=... has been removed. %s", option.name, option.message); return false; } } ResourceMark rm(THREAD); oop message = java_lang_Throwable::message(PENDING_EXCEPTION); if (message != NULL) { const char* msg = java_lang_String::as_utf8_string(message); log_error(arguments) ("%s", msg); } CLEAR_PENDING_EXCEPTION; return false; } return true; } jlong JfrOptionSet::_max_chunk_size = 0; jlong JfrOptionSet::_global_buffer_size = 0; jlong JfrOptionSet::_thread_buffer_size = 0; jlong JfrOptionSet::_memory_size = 0; jlong JfrOptionSet::_num_global_buffers = 0; jlong JfrOptionSet::_old_object_queue_size = 0; u4 JfrOptionSet::_stack_depth = STACK_DEPTH_DEFAULT; jboolean JfrOptionSet::_sample_threads = JNI_TRUE; jboolean JfrOptionSet::_retransform = JNI_TRUE; #ifdef ASSERT jboolean JfrOptionSet::_sample_protection = JNI_FALSE; #else jboolean JfrOptionSet::_sample_protection = JNI_TRUE; #endif bool JfrOptionSet::initialize(Thread* thread) { register_parser_options(); if (!parse_flight_recorder_options_internal(thread)) { return false; } if (_dcmd_retransform.is_set()) { set_retransform(_dcmd_retransform.value()); } set_old_object_queue_size(_dcmd_old_object_queue_size.value()); return adjust_memory_options(); } bool JfrOptionSet::configure(TRAPS) { if (FlightRecorderOptions == NULL) { return true; } ResourceMark rm(THREAD); bufferedStream st; // delegate to DCmd execution JfrConfigureFlightRecorderDCmd configure(&st, false); configure._repository_path.set_is_set(_dcmd_repository.is_set()); char* repo = _dcmd_repository.value(); if (repo != NULL) { const size_t len = strlen(repo); char* repo_copy = JfrCHeapObj::new_array(len + 1); if (NULL == repo_copy) { return false; } strncpy(repo_copy, repo, len + 1); configure._repository_path.set_value(repo_copy); } configure._stack_depth.set_is_set(_dcmd_stackdepth.is_set()); configure._stack_depth.set_value(_dcmd_stackdepth.value()); configure._thread_buffer_size.set_is_set(_dcmd_threadbuffersize.is_set()); configure._thread_buffer_size.set_value(_dcmd_threadbuffersize.value()._size); configure._global_buffer_count.set_is_set(_dcmd_numglobalbuffers.is_set()); configure._global_buffer_count.set_value(_dcmd_numglobalbuffers.value()); configure._global_buffer_size.set_is_set(_dcmd_globalbuffersize.is_set()); configure._global_buffer_size.set_value(_dcmd_globalbuffersize.value()._size); configure._max_chunk_size.set_is_set(_dcmd_maxchunksize.is_set()); configure._max_chunk_size.set_value(_dcmd_maxchunksize.value()._size); configure._memory_size.set_is_set(_dcmd_memorysize.is_set()); configure._memory_size.set_value(_dcmd_memorysize.value()._size); configure._sample_threads.set_is_set(_dcmd_sample_threads.is_set()); configure._sample_threads.set_value(_dcmd_sample_threads.value()); configure.execute(DCmd_Source_Internal, THREAD); if (HAS_PENDING_EXCEPTION) { java_lang_Throwable::print(PENDING_EXCEPTION, tty); CLEAR_PENDING_EXCEPTION; return false; } return true; } template static julong divide_with_user_unit(Argument& memory_argument, julong value) { if (memory_argument.value()._size != memory_argument.value()._val) { switch (memory_argument.value()._multiplier) { case 'k': case 'K': return value / K; case 'm': case 'M': return value / M; case 'g': case 'G': return value / G; } } return value; } template static void log_lower_than_min_value(Argument& memory_argument, julong min_value) { if (memory_argument.value()._size != memory_argument.value()._val) { // has multiplier log_error(arguments) ( "This value is lower than the minimum size required " JULONG_FORMAT "%c", divide_with_user_unit(memory_argument, min_value), memory_argument.value()._multiplier); return; } log_error(arguments) ( "This value is lower than the minimum size required " JULONG_FORMAT, divide_with_user_unit(memory_argument, min_value)); } template static void log_set_value(Argument& memory_argument) { if (memory_argument.value()._size != memory_argument.value()._val) { // has multiplier log_error(arguments) ( "Value specified for option \"%s\" is " JULONG_FORMAT "%c", memory_argument.name(), memory_argument.value()._val, memory_argument.value()._multiplier); return; } log_error(arguments) ( "Value specified for option \"%s\" is " JULONG_FORMAT, memory_argument.name(), memory_argument.value()._val); } template static void log_adjustments(MemoryArg& original_memory_size, julong new_memory_size, const char* msg) { log_trace(arguments) ( "%s size (original) " JULONG_FORMAT " B (user defined: %s)", msg, original_memory_size.value()._size, original_memory_size.is_set() ? "true" : "false"); log_trace(arguments) ( "%s size (adjusted) " JULONG_FORMAT " B (modified: %s)", msg, new_memory_size, original_memory_size.value()._size != new_memory_size ? "true" : "false"); log_trace(arguments) ( "%s size (adjustment) %s" JULONG_FORMAT " B", msg, new_memory_size < original_memory_size.value()._size ? "-" : "+", new_memory_size < original_memory_size.value()._size ? original_memory_size.value()._size - new_memory_size : new_memory_size - original_memory_size.value()._size); } // All "triangular" options are explicitly set // check that they are congruent and not causing // an ambiguous situtation template static bool check_for_ambiguity(MemoryArg& memory_size, MemoryArg& global_buffer_size, NumberArg& num_global_buffers) { assert(memory_size.is_set(), "invariant"); assert(global_buffer_size.is_set(), "invariant"); assert(num_global_buffers.is_set(), "invariant"); const julong calc_size = global_buffer_size.value()._size * (julong)num_global_buffers.value(); if (calc_size != memory_size.value()._size) { // ambiguous log_set_value(global_buffer_size); log_error(arguments) ( "Value specified for option \"%s\" is " JLONG_FORMAT, num_global_buffers.name(), num_global_buffers.value()); log_set_value(memory_size); log_error(arguments) ( "These values are causing an ambiguity when trying to determine how much memory to use"); log_error(arguments) ("\"%s\" * \"%s\" do not equal \"%s\"", global_buffer_size.name(), num_global_buffers.name(), memory_size.name()); log_error(arguments) ( "Try to remove one of the involved options or make sure they are unambigous"); return false; } return true; } template static bool ensure_minimum_count(Argument& buffer_count_argument, jlong min_count) { if (buffer_count_argument.value() < min_count) { log_error(arguments) ( "Value specified for option \"%s\" is " JLONG_FORMAT, buffer_count_argument.name(), buffer_count_argument.value()); log_error(arguments) ( "This value is lower than the minimum required number " JLONG_FORMAT, min_count); return false; } return true; } // global buffer size and num global buffers specified // ensure that particular combination to be ihigher than minimum memory size template static bool ensure_calculated_gteq(MemoryArg& global_buffer_size, NumberArg& num_global_buffers, julong min_value) { assert(global_buffer_size.is_set(), "invariant"); assert(num_global_buffers.is_set(), "invariant"); const julong calc_size = global_buffer_size.value()._size * (julong)num_global_buffers.value(); if (calc_size < min_value) { log_set_value(global_buffer_size); log_error(arguments) ( "Value specified for option \"%s\" is " JLONG_FORMAT, num_global_buffers.name(), num_global_buffers.value()); log_error(arguments) ("\"%s\" * \"%s\" (" JULONG_FORMAT ") is lower than minimum memory size required " JULONG_FORMAT, global_buffer_size.name(), num_global_buffers.name(), calc_size, min_value); return false; } return true; } template static bool ensure_first_gteq_second(Argument& first_argument, Argument& second_argument) { if (second_argument.value()._size > first_argument.value()._size) { log_set_value(first_argument); log_set_value(second_argument); log_error(arguments) ( "The value for option \"%s\" should not be larger than the value specified for option \"%s\"", second_argument.name(), first_argument.name()); return false; } return true; } static bool valid_memory_relations(const JfrMemoryOptions& options) { if (options.global_buffer_size_configured) { if (options.memory_size_configured) { if (!ensure_first_gteq_second(_dcmd_memorysize, _dcmd_globalbuffersize)) { return false; } } if (options.thread_buffer_size_configured) { if (!ensure_first_gteq_second(_dcmd_globalbuffersize, _dcmd_threadbuffersize)) { return false; } } if (options.buffer_count_configured) { if (!ensure_calculated_gteq(_dcmd_globalbuffersize, _dcmd_numglobalbuffers, MIN_MEMORY_SIZE)) { return false; } } } return true; } static void post_process_adjusted_memory_options(const JfrMemoryOptions& options) { assert(options.memory_size >= MIN_MEMORY_SIZE, "invariant"); assert(options.global_buffer_size >= MIN_GLOBAL_BUFFER_SIZE, "invariant"); assert(options.buffer_count >= MIN_BUFFER_COUNT, "invariant"); assert(options.thread_buffer_size >= MIN_THREAD_BUFFER_SIZE, "invariant"); log_adjustments(_dcmd_memorysize, options.memory_size, "Memory"); log_adjustments(_dcmd_globalbuffersize, options.global_buffer_size, "Global buffer"); log_adjustments(_dcmd_threadbuffersize, options.thread_buffer_size, "Thread local buffer"); log_trace(arguments) ("Number of global buffers (original) " JLONG_FORMAT " (user defined: %s)", _dcmd_numglobalbuffers.value(), _dcmd_numglobalbuffers.is_set() ? "true" : "false"); log_trace(arguments) ( "Number of global buffers (adjusted) " JULONG_FORMAT " (modified: %s)", options.buffer_count, _dcmd_numglobalbuffers.value() != (jlong)options.buffer_count ? "true" : "false"); log_trace(arguments) ("Number of global buffers (adjustment) %s" JLONG_FORMAT, (jlong)options.buffer_count < _dcmd_numglobalbuffers.value() ? "" : "+", (jlong)options.buffer_count - _dcmd_numglobalbuffers.value()); MemorySizeArgument adjusted_memory_size; adjusted_memory_size._val = divide_with_user_unit(_dcmd_memorysize, options.memory_size); adjusted_memory_size._multiplier = _dcmd_memorysize.value()._multiplier; adjusted_memory_size._size = options.memory_size; MemorySizeArgument adjusted_global_buffer_size; adjusted_global_buffer_size._val = divide_with_user_unit(_dcmd_globalbuffersize, options.global_buffer_size); adjusted_global_buffer_size._multiplier = _dcmd_globalbuffersize.value()._multiplier; adjusted_global_buffer_size._size = options.global_buffer_size; MemorySizeArgument adjusted_thread_buffer_size; adjusted_thread_buffer_size._val = divide_with_user_unit(_dcmd_threadbuffersize, options.thread_buffer_size); adjusted_thread_buffer_size._multiplier = _dcmd_threadbuffersize.value()._multiplier; adjusted_thread_buffer_size._size = options.thread_buffer_size; // store back to dcmd _dcmd_memorysize.set_value(adjusted_memory_size); _dcmd_memorysize.set_is_set(true); _dcmd_globalbuffersize.set_value(adjusted_global_buffer_size); _dcmd_globalbuffersize.set_is_set(true); _dcmd_numglobalbuffers.set_value((jlong)options.buffer_count); _dcmd_numglobalbuffers.set_is_set(true); _dcmd_threadbuffersize.set_value(adjusted_thread_buffer_size); _dcmd_threadbuffersize.set_is_set(true); } static void initialize_memory_options_from_dcmd(JfrMemoryOptions& options) { options.memory_size = _dcmd_memorysize.value()._size; options.global_buffer_size = MAX2(_dcmd_globalbuffersize.value()._size, (julong)os::vm_page_size()); options.buffer_count = (julong)_dcmd_numglobalbuffers.value(); options.thread_buffer_size = MAX2(_dcmd_threadbuffersize.value()._size, (julong)os::vm_page_size()); // determine which options have been explicitly set options.memory_size_configured = _dcmd_memorysize.is_set(); options.global_buffer_size_configured = _dcmd_globalbuffersize.is_set(); options.buffer_count_configured = _dcmd_numglobalbuffers.is_set(); options.thread_buffer_size_configured = _dcmd_threadbuffersize.is_set(); assert(options.memory_size >= MIN_MEMORY_SIZE, "invariant"); assert(options.global_buffer_size >= MIN_GLOBAL_BUFFER_SIZE, "invariant"); assert(options.buffer_count >= MIN_BUFFER_COUNT, "invariant"); assert(options.thread_buffer_size >= MIN_THREAD_BUFFER_SIZE, "invariant"); } template static bool ensure_gteq(Argument& memory_argument, const jlong value) { if ((jlong)memory_argument.value()._size < value) { log_set_value(memory_argument); log_lower_than_min_value(memory_argument, value); return false; } return true; } static bool ensure_valid_minimum_sizes() { // ensure valid minimum memory sizes if (_dcmd_memorysize.is_set()) { if (!ensure_gteq(_dcmd_memorysize, MIN_MEMORY_SIZE)) { return false; } } if (_dcmd_globalbuffersize.is_set()) { if (!ensure_gteq(_dcmd_globalbuffersize, MIN_GLOBAL_BUFFER_SIZE)) { return false; } } if (_dcmd_numglobalbuffers.is_set()) { if (!ensure_minimum_count(_dcmd_numglobalbuffers, MIN_BUFFER_COUNT)) { return false; } } if (_dcmd_threadbuffersize.is_set()) { if (!ensure_gteq(_dcmd_threadbuffersize, MIN_THREAD_BUFFER_SIZE)) { return false; } } return true; } /** * Starting with the initial set of memory values from the user, * sanitize, enforce min/max rules and adjust to a set of consistent options. * * Adjusted memory sizes will be page aligned. */ bool JfrOptionSet::adjust_memory_options() { if (!ensure_valid_minimum_sizes()) { return false; } JfrMemoryOptions options; initialize_memory_options_from_dcmd(options); if (!valid_memory_relations(options)) { return false; } if (!JfrMemorySizer::adjust_options(&options)) { if (!check_for_ambiguity(_dcmd_memorysize, _dcmd_globalbuffersize, _dcmd_numglobalbuffers)) { return false; } } post_process_adjusted_memory_options(options); return true; } /* to support starting multiple startup recordings static const char* start_flight_recording_option_original = NULL; static const char* flight_recorder_option_original = NULL; static void copy_option_string(const JavaVMOption* option, const char** addr) { assert(option != NULL, "invariant"); assert(option->optionString != NULL, "invariant"); const size_t length = strlen(option->optionString); *addr = JfrCHeapObj::new_array(length + 1); assert(*addr != NULL, "invarinat"); strncpy((char*)*addr, option->optionString, length + 1); assert(strncmp(*addr, option->optionString, length + 1) == 0, "invariant"); } copy_option_string(*option, &start_flight_recording_option_original); copy_option_string(*option, &flight_recorder_option_original); */ bool JfrOptionSet::parse_start_flight_recording(const JavaVMOption** option, char* tail) { assert(option != NULL, "invariant"); assert(tail != NULL, "invariant"); assert((*option)->optionString != NULL, "invariant"); assert(strncmp((*option)->optionString, "-XX:StartFlightRecording", 24) == 0, "invariant"); if (*tail == '\0') { // Add dummy dumponexit=false so -XX:StartFlightRecording can be used without a parameter. // The existing option->optionString points to stack memory so no need to deallocate. const_cast(*option)->optionString = (char*)"-XX:StartFlightRecording=dumponexit=false"; } else { *tail = '='; // ":" -> "=" } return false; } bool JfrOptionSet::parse_flight_recorder_options(const JavaVMOption** option, char* tail) { assert(option != NULL, "invariant"); assert(tail != NULL, "invariant"); assert((*option)->optionString != NULL, "invariant"); assert(strncmp((*option)->optionString, "-XX:FlightRecorderOptions", 25) == 0, "invariant"); if (tail != NULL) { *tail = '='; // ":" -> "=" } return false; }