diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 133184ed888..c2ca22b5d48 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -3907,7 +3907,7 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) { return result; } if (QuickStart::is_enabled()) { - QuickStart::post_process_arguments(); + QuickStart::post_process_arguments(cur_cmd_args); } // Delay warning until here so that we've had a chance to process diff --git a/src/hotspot/share/runtime/quickStart.cpp b/src/hotspot/share/runtime/quickStart.cpp index 241f950aa80..4bbae9d88e0 100644 --- a/src/hotspot/share/runtime/quickStart.cpp +++ b/src/hotspot/share/runtime/quickStart.cpp @@ -6,7 +6,9 @@ #include "runtime/arguments.hpp" #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" +#include "runtime/os.inline.hpp" #include "runtime/quickStart.hpp" +#include "runtime/vm_version.hpp" #include "utilities/defaultStream.hpp" bool QuickStart::_is_starting = true; @@ -18,24 +20,43 @@ bool QuickStart::_need_destroy = false; QuickStart::QuickStartRole QuickStart::_role = QuickStart::Normal; const char* QuickStart::_cache_path = NULL; -const char* QuickStart::_image_env = NULL; +const char* QuickStart::_image_id = NULL; +const char* QuickStart::_vm_version = NULL; const char* QuickStart::_lock_path = NULL; const char* QuickStart::_temp_metadata_file_path = NULL; const char* QuickStart::_metadata_file_path = NULL; + +int QuickStart::_features = 0; +int QuickStart::_jvm_option_count = 0; + const char* QuickStart::_opt_name[] = { #define OPT_TAG(name) #name, OPT_TAG_LIST #undef OPT_TAG }; +enum identifier { + Features, + VMVersion, + ContainerImageID, + JVMOptionCount +}; + +const char* QuickStart::_identifier_name[] = { + "Features: ", + "VM_Version: ", + "Container_Image_ID: ", + "JVM_Option_Count: " +}; + bool QuickStart::_opt_enabled[] = { #define OPT_TAG(name) true, OPT_TAG_LIST #undef OPT_TAG }; -FILE *QuickStart::_METADATA_FILE = NULL; -FILE *QuickStart::_TEMP_METADATA_FILE = NULL; +FILE* QuickStart::_metadata_file = NULL; +fileStream* QuickStart::_temp_metadata_file = NULL; int QuickStart::_lock_file_fd = 0; @@ -71,15 +92,30 @@ bool QuickStart::parse_command_line_arguments(const char* options) { print_command_line_help(&stream); vm_exit(0); } else if (match_option(cur, "verbose", &tail)) { + if (tail[0] != '\0') { + success = false; + tty->print_cr("[QuickStart] Invalid -Xquickstart option '%s'", cur); + } _verbose = true; } else if (match_option(cur, "printStat", &tail)) { + if (tail[0] != '\0') { + success = false; + tty->print_cr("[QuickStart] Invalid -Xquickstart option '%s'", cur); + } _print_stat_enabled = true; } else if (match_option(cur, "destroy", &tail)) { + if (tail[0] != '\0') { + success = false; + tty->print_cr("[QuickStart] Invalid -Xquickstart option '%s'", cur); + } _need_destroy = true; } else if (match_option(cur, "path=", &tail)) { _cache_path = os::strdup_check_oom(tail, mtArguments); - } else if (match_option(cur, "dockerImageEnv=", &tail)) { - _image_env = os::strdup_check_oom(tail, mtArguments); + } else if (match_option(cur, "containerImageEnv=", &tail)) { + char *buffer = ::getenv(tail); + if (buffer != NULL) { + _image_id = os::strdup_check_oom(buffer, mtArguments); + } } else { success = false; tty->print_cr("[QuickStart] Invalid -Xquickstart option '%s'", cur); @@ -125,7 +161,7 @@ void QuickStart::print_command_line_help(outputStream* out) { out->print_cr(" path= Specify the location of the cache files"); out->print_cr(" destroy Destroy the cache files (use specified path or default)"); out->print_cr(" +/- Enable/disable the specific optimization"); - out->print_cr(" dockerImageEnv= Specify the environment variable to get the unique identifier of the container"); + out->print_cr(" containerImageEnv= Specify the environment variable to get the unique identifier of the container"); out->cr(); out->print_cr("Available optimization:"); @@ -144,27 +180,113 @@ void QuickStart::initialize(TRAPS) { vmSymbols::boolean_String_void_signature(), &args, CHECK); } -void QuickStart::post_process_arguments() { +void QuickStart::post_process_arguments(JavaVMInitArgs* options_args) { // Prepare environment calculate_cache_path(); // destroy the cache directory destroy_cache_folder(); // Determine the role - if (!determine_tracer_or_replayer()) { + if (!determine_tracer_or_replayer(options_args)) { _role = Normal; return; } // Process argument for each optimization process_argument_for_optimaztion(); +} - if (_role == Tracer) { - // Temporarily put here to ensure the integrity of the test - generate_metadata_file(); +bool QuickStart::check_integrity(JavaVMInitArgs* options_args) { + if (_print_stat_enabled) { + print_stat(true); } + + _metadata_file = os::fopen(_metadata_file_path, "r"); + if (!_metadata_file) { + // if one process removes metadata here, will NULL. + log("metadata file may be destroyed by another process."); + return false; + } + bool result = load_and_validate(options_args); + + ::fclose(_metadata_file); + return result; } -bool QuickStart::check_integrity() { - // check the integrity +bool QuickStart::load_and_validate(JavaVMInitArgs* options_args) { + char line[PATH_MAX]; + const char* tail = NULL; + bool feature_checked = false; + bool version_checked = false; + bool container_checked = false; + bool option_checked = false; + + _vm_version = VM_Version::internal_vm_info_string(); + + while (fgets(line, sizeof(line), _metadata_file) != NULL) { + + if (!feature_checked && match_option(line, _identifier_name[Features], &tail)) { + // read features + if (sscanf(tail, "%d", &_features) != 1) { + log("Unable to read the features."); + return false; + } + feature_checked = true; + } else if (!version_checked && match_option(line, _identifier_name[VMVersion], &tail)) { + // read jvm info + if (options_args != NULL && strncmp(tail, _vm_version, strlen(_vm_version)) != 0) { + log("VM Version isn't the same."); + return false; + } + version_checked = true; + } else if (!container_checked && match_option(line, _identifier_name[ContainerImageID], &tail)) { + container_checked = true; + // read image info + if (options_args == NULL) { + continue; + } + // ignore \n + int size = strlen(tail) - 1; + const char *image_ident = QuickStart::image_id(); + int ident_size = image_ident != NULL ? strlen(image_ident) : 0; + if (size != ident_size) { + QuickStart::log("Container image isn't the same."); + return false; + } + + if (strncmp(tail, QuickStart::image_id(), size) != 0) { + log("Container image isn't the same."); + return false; + } + } else if (!option_checked && match_option(line, _identifier_name[JVMOptionCount], &tail)) { + // read options args + if (sscanf(tail, "%d", &_jvm_option_count) != 1) { + log("Unable to read the option number."); + return false; + } + option_checked = true; + if (options_args != NULL) { + int option_count = options_args->nOptions > 2 ? options_args->nOptions - 2 : 0; + if (_jvm_option_count != option_count) { + log("JVM option isn't the same."); + return false; + } + } + for (int index = 0; index < _jvm_option_count; index++) { + if (fgets(line, sizeof(line), _metadata_file) == NULL) { + log("Unable to read JVM option."); + return false; + } + + if (options_args == NULL) { + continue; + } + const JavaVMOption *option = options_args->options + index; + if (strncmp(line, option->optionString, strlen(option->optionString)) != 0) { + log("JVM option isn't the same."); + return false; + } + } + } + } return true; } @@ -197,6 +319,29 @@ void QuickStart::destroy_cache_folder() { } } +void QuickStart::print_stat(bool isReplayer) { + if (!_print_stat_enabled) { + return; + } + if (isReplayer) { + _metadata_file = os::fopen(_metadata_file_path, "r"); + if (_metadata_file) { + bool result = load_and_validate(NULL); + ::fclose(_metadata_file); + if (result) { + // print cache information for replayer + jio_fprintf(defaultStream::output_stream(), "[QuickStart] Current statistics for cache %s\n", _cache_path); + jio_fprintf(defaultStream::output_stream(), "\n"); + jio_fprintf(defaultStream::output_stream(), "Cache created with:\n"); + vm_exit(0); + } + } + } + + jio_fprintf(defaultStream::output_stream(), "[QuickStart] There is no cache in %s\n", _cache_path); + vm_exit(0); +} + void QuickStart::process_argument_for_optimaztion() { switch(_role) { case Replayer: @@ -208,11 +353,14 @@ void QuickStart::process_argument_for_optimaztion() { } } -bool QuickStart::determine_tracer_or_replayer() { +bool QuickStart::determine_tracer_or_replayer(JavaVMInitArgs* options_args) { struct stat st; char buf[PATH_MAX]; int ret = os::stat(_cache_path, &st); if (ret != 0) { + if (_print_stat_enabled) { + print_stat(false); + } ret = ::mkdir(_cache_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if (ret != 0) { log("Could not mkdir [%s] because [%s]", _cache_path, os::strerror(errno)); @@ -231,6 +379,9 @@ bool QuickStart::determine_tracer_or_replayer() { _metadata_file_path = os::strdup_check_oom(buf, mtArguments); ret = os::stat(_metadata_file_path, &st); if (ret < 0 && errno == ENOENT) { + if (_print_stat_enabled) { + print_stat(false); + } // Create a LOCK file jio_snprintf(buf, PATH_MAX, "%s%s%s", _cache_path, os::file_separator(), LOCK_FILE); _lock_path = os::strdup_check_oom(buf, mtArguments); @@ -248,15 +399,19 @@ bool QuickStart::determine_tracer_or_replayer() { jio_fprintf(defaultStream::error_stream(), "[QuickStart] The %s file exists\n", TEMP_METADATA_FILE); return false; } - _TEMP_METADATA_FILE = os::fopen(buf, "w"); - if (!_TEMP_METADATA_FILE) { + _temp_metadata_file = new(ResourceObj::C_HEAP, mtInternal) fileStream(_temp_metadata_file_path, "w"); + if (!_temp_metadata_file) { jio_fprintf(defaultStream::error_stream(), "[QuickStart] Failed to create %s file\n", TEMP_METADATA_FILE); return false; } + if (!dump_cached_info(options_args)) { + jio_fprintf(defaultStream::error_stream(), "[QuickStart] Failed to dump cached information\n"); + return false; + } _role = Tracer; log("Running as tracer"); return true; - } else if (ret == 0 && check_integrity()) { + } else if (ret == 0 && check_integrity(options_args)) { _role = Replayer; log("Running as replayer"); return true; @@ -266,7 +421,7 @@ bool QuickStart::determine_tracer_or_replayer() { void QuickStart::generate_metadata_file() { // mv metadata to metadata.tmp - ::fclose(_TEMP_METADATA_FILE); + delete _temp_metadata_file; int ret = ::rename(_temp_metadata_file_path, _metadata_file_path); if (ret != 0) { jio_fprintf(defaultStream::error_stream(), @@ -348,5 +503,44 @@ int QuickStart::remove_dir(const char* dir) { } void QuickStart::notify_dump() { - log("startup finishes"); + if (_role == Tracer) { + generate_metadata_file(); + } + log("notifying dump done."); +} + +bool QuickStart::dump_cached_info(JavaVMInitArgs* options_args) { + if (_temp_metadata_file == NULL) { + return false; + } + _vm_version = VM_Version::internal_vm_info_string(); + _features = 0; + + // calculate argument, ignore the last two option: + // -Dsun.java.launcher=SUN_STANDARD + // -Dsun.java.launcher.pid= + _jvm_option_count = options_args->nOptions > 2 ? options_args->nOptions - 2 : 0; + + _temp_metadata_file->print_cr("%s%d", _identifier_name[Features], _features); + // write jvm info + _temp_metadata_file->print_cr("%s%s", _identifier_name[VMVersion], _vm_version); + + // write image info + const char *image_ident = QuickStart::image_id(); + if (image_ident != NULL) { + _temp_metadata_file->print_cr("%s%s", _identifier_name[ContainerImageID], image_ident); + } else { + _temp_metadata_file->print_cr("%s", _identifier_name[ContainerImageID]); + } + + _temp_metadata_file->print_cr("%s%d", _identifier_name[JVMOptionCount], _jvm_option_count); + // write options args + for (int index = 0; index < _jvm_option_count; index++) { + const JavaVMOption *option = options_args->options + index; + _temp_metadata_file->print_cr("%s", option->optionString); + } + + _temp_metadata_file->flush(); + return true; } + diff --git a/src/hotspot/share/runtime/quickStart.hpp b/src/hotspot/share/runtime/quickStart.hpp index 56f012ac1a9..0f727b871cb 100644 --- a/src/hotspot/share/runtime/quickStart.hpp +++ b/src/hotspot/share/runtime/quickStart.hpp @@ -3,6 +3,7 @@ #include "memory/allocation.hpp" #include "utilities/growableArray.hpp" +#include "utilities/ostream.hpp" #define OPT_TAG_LIST_EXT @@ -24,27 +25,30 @@ class QuickStart : AllStatic { Replayer } QuickStartRole; - ~QuickStart(); static const char* cache_path() { return _cache_path; } static bool is_enabled() { return _is_enabled; } + static bool verbose() { return _verbose; } static bool parse_command_line_arguments(const char* opts = NULL); - static void post_process_arguments(); + static void post_process_arguments(JavaVMInitArgs* options_args); static void initialize(TRAPS); static bool is_tracer() { return _role == Tracer; } static bool is_replayer() { return _role == Replayer; } static bool is_starting() { return is_enabled() && _is_starting; } static int remove_dir(const char* dir); + static const char* image_id() { return _image_id; } + static const char* vm_version() { return _vm_version; } private: static const char* _cache_path; - static const char* _image_env; + static const char* _image_id; + static const char* _vm_version; static const char* _lock_path; static const char* _temp_metadata_file_path; static const char* _metadata_file_path; - static FILE *_METADATA_FILE; - static FILE *_TEMP_METADATA_FILE; + static FILE* _metadata_file; + static fileStream* _temp_metadata_file; static int _lock_file_fd; @@ -56,17 +60,23 @@ class QuickStart : AllStatic { static bool _print_stat_enabled; static bool _need_destroy; static const char* _opt_name[]; + static const char* _identifier_name[]; static bool _opt_enabled[]; + static int _features; + static int _jvm_option_count; static bool set_optimization(const char* option, bool enabled); - static bool determine_tracer_or_replayer(); + static bool determine_tracer_or_replayer(JavaVMInitArgs* options_args); static void calculate_cache_path(); static void destroy_cache_folder(); static void process_argument_for_optimaztion(); - static bool check_integrity(); + static bool check_integrity(JavaVMInitArgs* options_args); static void generate_metadata_file(); static bool match_option(const char* option, const char* name, const char** tail); static void print_command_line_help(outputStream* out); + static bool dump_cached_info(JavaVMInitArgs* options_args); + static bool load_and_validate(JavaVMInitArgs* options_args); + static void print_stat(bool isReplayer); static void log(const char* msg, ...) ATTRIBUTE_PRINTF(1, 2); public: diff --git a/test/jdk/com/alibaba/quickstart/TestIntegrityCheck.java b/test/jdk/com/alibaba/quickstart/TestIntegrityCheck.java new file mode 100644 index 00000000000..3a0c169b7d9 --- /dev/null +++ b/test/jdk/com/alibaba/quickstart/TestIntegrityCheck.java @@ -0,0 +1,55 @@ +/* + * @test + * @summary Test Integrity Check + * @library /test/lib + * @run main/othervm TestIntegrityCheck + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestIntegrityCheck { + private static String cachepath = System.getProperty("user.dir"); + public static void main(String[] args) throws Exception { + TestIntegrityCheck test = new TestIntegrityCheck(); + cachepath = cachepath + "/integrityCheck"; + test.verifyIntegrity(); + test.verifyImageEnvChange(); + test.verifyOptionChange(); + } + + void verifyIntegrity() throws Exception { + runAsTracer(); + runAsReplayer(); + } + + void verifyImageEnvChange() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose,containerImageEnv=pouchid", "-version"); + pb.environment().put("pouchid", "123456"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Container image isn't the same"); + output.shouldHaveExitValue(0); + } + + void verifyOptionChange() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose,containerImageEnv=pouchid", "-esa", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("JVM option isn't the same"); + output.shouldHaveExitValue(0); + } + + void runAsTracer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose,containerImageEnv=pouchid", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Running as tracer"); + output.shouldHaveExitValue(0); + } + + void runAsReplayer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose,containerImageEnv=pouchid", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Running as replayer"); + output.shouldHaveExitValue(0); + } +} + diff --git a/test/jdk/com/alibaba/quickstart/TestNotifyDump.java b/test/jdk/com/alibaba/quickstart/TestNotifyDump.java index 61e7566cb1b..20f72c4a7e5 100644 --- a/test/jdk/com/alibaba/quickstart/TestNotifyDump.java +++ b/test/jdk/com/alibaba/quickstart/TestNotifyDump.java @@ -20,13 +20,14 @@ public class TestNotifyDump { public static void main(String[] args) throws Exception { String dir = AccessController.doPrivileged(new GetPropertyAction("test.classes")); + TestNotifyDump.destroyCache(dir); TestNotifyDump.verifyPathSetting(dir); new File(dir).delete(); } static void verifyPathSetting(String parentDir) throws Exception { ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( - "-Xquickstart:path=" + parentDir + "/quickstartcache", + "-Xquickstart:path=" + parentDir + "/testnotifydump", "-Xquickstart:verbose", // In sleeping condition there is no classloading happens, // we will consider it as the start-up finish @@ -39,4 +40,10 @@ static void verifyPathSetting(String parentDir) throws Exception { output.shouldHaveExitValue(0); } + static void destroyCache(String parentDir) throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + parentDir + "/testnotifydump", "-Xquickstart:verbose,destroy", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("destory the cache folder"); + output.shouldHaveExitValue(0); + } } diff --git a/test/jdk/com/alibaba/quickstart/TestPrintStat.java b/test/jdk/com/alibaba/quickstart/TestPrintStat.java new file mode 100644 index 00000000000..4dec1db08a4 --- /dev/null +++ b/test/jdk/com/alibaba/quickstart/TestPrintStat.java @@ -0,0 +1,42 @@ +/* + * @test + * @summary Test -Xquickstart:printStat option + * @library /test/lib + * @run main/othervm TestPrintStat + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestPrintStat { + private static String cachepath = System.getProperty("user.dir"); + public static void main(String[] args) throws Exception { + TestPrintStat test = new TestPrintStat(); + cachepath = cachepath + "/printstat"; + test.runWithoutCache(); + test.runAsTracer(); + test.runAsReplayerWithPrintStat(); + } + + void runWithoutCache() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:printStat", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("[QuickStart] There is no cache in " + cachepath); + output.shouldHaveExitValue(0); + } + + void runAsTracer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Running as tracer"); + output.shouldHaveExitValue(0); + } + + void runAsReplayerWithPrintStat() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:printStat", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("[QuickStart] Current statistics for cache " + cachepath); + output.shouldHaveExitValue(0); + } +} +