diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index e086b18f8fd..b9bdbe2480a 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -367,14 +367,15 @@ GrowableArray* JfrDCmd::argument_name_array() const { JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), _repository_path("repositorypath", "Path to repository,.e.g \\\"My Repository\\\"", "STRING", false, NULL), - _dump_path("dumppath", "Path to dump,.e.g \\\"My Dump path\\\"", "STRING", false, NULL), - _stack_depth("stackdepth", "Stack Depth", "JULONG", false, "64"), + _dump_path("dumppath", "Path to dump, e.g. \\\"My Dump path\\\"", "STRING", false, NULL), + _stack_depth("stackdepth", "Stack depth", "JULONG", false, "64"), _global_buffer_count("globalbuffercount", "Number of global buffers,", "JULONG", false, "20"), _global_buffer_size("globalbuffersize", "Size of a global buffers,", "MEMORY SIZE", false, "512k"), _thread_buffer_size("thread_buffer_size", "Size of a thread buffer", "MEMORY SIZE", false, "8k"), _memory_size("memorysize", "Overall memory size, ", "MEMORY SIZE", false, "10m"), _max_chunk_size("maxchunksize", "Size of an individual disk chunk", "MEMORY SIZE", false, "12m"), - _sample_threads("samplethreads", "Activate Thread sampling", "BOOLEAN", false, "true"), + _sample_threads("samplethreads", "Activate thread sampling", "BOOLEAN", false, "true"), + _preserve_repository("preserve-repository", "Preserve the disk repository after JVM exit", "BOOLEAN", false, "false"), _verbose(true) { _dcmdparser.add_dcmd_option(&_repository_path); _dcmdparser.add_dcmd_option(&_dump_path); @@ -385,6 +386,7 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out _dcmdparser.add_dcmd_option(&_memory_size); _dcmdparser.add_dcmd_option(&_max_chunk_size); _dcmdparser.add_dcmd_option(&_sample_threads); + _dcmdparser.add_dcmd_option(&_preserve_repository); }; void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { @@ -392,49 +394,52 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { // 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(" initialized. (STRING, default determined by the value for"); - out->print_cr(" memorysize)"); + 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(" initialized. (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 initialized. (STRING, default determined by the value for"); - out->print_cr(" memorysize)"); + 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 initialized. (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(" 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(" 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(" 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(" dumppath (Optional) Path to the location where a recording file is written"); - out->print_cr(" in case the VM runs into a critical error, such as a system"); - out->print_cr(" crash. (STRING, The default location is the current directory)"); + out->print_cr(" dumppath (Optional) Path to the location where a recording file is written"); + out->print_cr(" in case the VM runs into a critical error, such as a system"); + out->print_cr(" crash. (STRING, The default location is the current directory)"); 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(" 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(" 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(" preserve-repository (Optional) Preserve files stored in the disk repository after the"); + out->print_cr(" Java Virtual Machine has exited. (BOOLEAN, false)"); out->print_cr(""); out->print_cr("Options must be specified using the or = syntax."); out->print_cr(""); @@ -480,6 +485,7 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { jobject thread_buffer_size = NULL; jobject max_chunk_size = NULL; jobject memory_size = NULL; + jobject preserve_repository = nullptr; if (!JfrRecorder::is_created()) { if (_stack_depth.is_set()) { @@ -510,12 +516,15 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { } } } + if (_preserve_repository.is_set()) { + preserve_repository = JfrJavaSupport::new_java_lang_Boolean(_preserve_repository.value(), CHECK); + } static const char klass[] = "jdk/jfr/internal/dcmd/DCmdConfigure"; static const char method[] = "execute"; static const char signature[] = "(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/Integer;" "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;" - "Ljava/lang/Long;)[Ljava/lang/String;"; + "Ljava/lang/Long;Ljava/lang/Boolean;)[Ljava/lang/String;"; JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); execute_args.set_receiver(h_dcmd_instance); @@ -530,6 +539,7 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { execute_args.push_jobject(thread_buffer_size); execute_args.push_jobject(memory_size); execute_args.push_jobject(max_chunk_size); + execute_args.push_jobject(preserve_repository); JfrJavaSupport::call_virtual(&execute_args, THREAD); handle_dcmd_result(output(), result.get_oop(), source, THREAD); diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp index 8d2ae20281c..7bd27bc1104 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp @@ -157,6 +157,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { DCmdArgument _memory_size; DCmdArgument _max_chunk_size; DCmdArgument _sample_threads; + DCmdArgument _preserve_repository; bool _verbose; public: @@ -177,7 +178,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; return p; } - static int num_arguments() { return 9; } + static int num_arguments() { return 10; } virtual void execute(DCmdSource source, TRAPS); virtual void print_help(const char* name) const; }; diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp index ad0ac3b11a3..fbcd28bd560 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp @@ -165,6 +165,7 @@ 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"; +const char* const default_preserve_repository = "false"; DEBUG_ONLY(const char* const default_sample_protection = "false";) // statics @@ -254,6 +255,13 @@ static DCmdArgument _dcmd_retransform( true, default_retransform); +static DCmdArgument _dcmd_preserve_repository( + "preserve-repository", + "Preserve disk repository after JVM exit", + "BOOLEAN", + false, + default_preserve_repository); + static DCmdParser _parser; static void register_parser_options() { @@ -268,6 +276,7 @@ static void register_parser_options() { _parser.add_dcmd_option(&_dcmd_sample_threads); _parser.add_dcmd_option(&_dcmd_retransform); _parser.add_dcmd_option(&_dcmd_old_object_queue_size); + _parser.add_dcmd_option(&_dcmd_preserve_repository); DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);) } @@ -379,6 +388,9 @@ bool JfrOptionSet::configure(TRAPS) { configure._sample_threads.set_is_set(_dcmd_sample_threads.is_set()); configure._sample_threads.set_value(_dcmd_sample_threads.value()); + configure._preserve_repository.set_is_set(_dcmd_preserve_repository.is_set()); + configure._preserve_repository.set_value(_dcmd_preserve_repository.value()); + configure.set_verbose(false); configure.execute(DCmd_Source_Internal, THREAD); diff --git a/src/java.base/share/man/java.1 b/src/java.base/share/man/java.1 index 49a4a32d150..71ed8d3c3f8 100644 --- a/src/java.base/share/man/java.1 +++ b/src/java.base/share/man/java.1 @@ -1337,6 +1337,12 @@ buffers. Maximum number of old objects to track. By default, the number of objects is set to 256. .TP +\f[V]preserve-repository=\f[R]{\f[V]true\f[R]|\f[V]false\f[R]} +Specifies whether files stored in the disk repository should be kept +after the JVM has exited. +If false, files are deleted. +By default, this parameter is disabled. +.TP \f[V]repository=\f[R]\f[I]path\f[R] Specifies the repository (a directory) for temporary disk storage. By default, the system\[aq]s temporary directory is used. diff --git a/src/jdk.jcmd/share/man/jcmd.1 b/src/jdk.jcmd/share/man/jcmd.1 index 2f61ed240ce..8c3a1be6d7d 100644 --- a/src/jdk.jcmd/share/man/jcmd.1 +++ b/src/jdk.jcmd/share/man/jcmd.1 @@ -402,6 +402,12 @@ On Linux operating systems, the temporary directory is \f[V]/tmp\f[R]. On Windwows, the temporary directory is specified by the \f[V]TMP\f[R] environment variable.) .IP \[bu] 2 +\f[V]preserve-repository=\f[R]{\f[V]true\f[R]|\f[V]false\f[R]} : +Specifies whether files stored in the disk repository should be kept +after the JVM has exited. +If false, files are deleted. +By default, this parameter is disabled. +.IP \[bu] 2 \f[V]stackdepth\f[R]: (Optional) Stack depth for stack traces. Setting this value greater than the default of 64 may cause a performance degradation. diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java index bcefa5b87df..28a64f1f1f8 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java @@ -52,6 +52,7 @@ public final class Options { private static final int DEFAULT_STACK_DEPTH = 64; private static final long DEFAULT_MAX_CHUNK_SIZE = 12 * 1024 * 1024; private static final SafePath DEFAULT_DUMP_PATH = null; + private static final boolean DEFAULT_PRESERVE_REPOSITORY = false; private static long memorySize; private static long globalBufferSize; @@ -59,6 +60,7 @@ public final class Options { private static long threadBufferSize; private static int stackDepth; private static long maxChunkSize; + private static boolean preserveRepository; static { final long pageSize = Unsafe.getUnsafe().pageSize(); @@ -138,6 +140,14 @@ public final class Options { return stackDepth; } + public static synchronized void setPreserveRepository(boolean preserve) { + preserveRepository = preserve; + } + + public static synchronized boolean getPreserveRepository() { + return preserveRepository; + } + private static synchronized void reset() { setMaxChunkSize(DEFAULT_MAX_CHUNK_SIZE); setMemorySize(DEFAULT_MEMORY_SIZE); @@ -150,6 +160,7 @@ public final class Options { } setStackDepth(DEFAULT_STACK_DEPTH); setThreadBufferSize(DEFAULT_THREAD_BUFFER_SIZE); + setPreserveRepository(DEFAULT_PRESERVE_REPOSITORY); } static synchronized long getWaitInterval() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java index 5efb322fb9d..8e11e4b4406 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java @@ -160,6 +160,10 @@ public final class Repository { } synchronized void clear() { + if (Options.getPreserveRepository()) { + return; + } + for (SafePath p : cleanupDirectories) { try { SecuritySupport.clearDirectory(p); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java index 69a0bd88378..c67c8a756c7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java @@ -51,9 +51,9 @@ final class DCmdConfigure extends AbstractDCmd { * @param globalBufferCount number of global buffers * @param globalBufferSize size of global buffers * @param threadBufferSize size of thread buffer for events + * @param memorySize Size of in memory buffer * @param maxChunkSize threshold at which a new chunk is created in the disk repository - * @param sampleThreads if thread sampling should be enabled - * + * @param preserveRepository if files in the repository should be deleted on exit. * @return result * @throws DCmdException @@ -69,7 +69,8 @@ final class DCmdConfigure extends AbstractDCmd { Long globalBufferSize, Long threadBufferSize, Long memorySize, - Long maxChunkSize + Long maxChunkSize, + Boolean preserveRepository ) throws DCmdException { if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) { @@ -80,7 +81,8 @@ final class DCmdConfigure extends AbstractDCmd { ", globalbuffersize=" + globalBufferSize + ", thread_buffer_size=" + threadBufferSize + ", memorysize=" + memorySize + - ", maxchunksize=" + maxChunkSize); + ", maxchunksize=" + maxChunkSize + + ", preserveRepository=" + preserveRepository); } @@ -103,6 +105,14 @@ final class DCmdConfigure extends AbstractDCmd { updated = true; } + if (preserveRepository != null) { + Options.setPreserveRepository(preserveRepository.booleanValue()); + if (verbose) { + printPreserveRepository(); + } + updated = true; + } + if (dumpPath != null) { try { Options.setDumpPath(new SafePath(dumpPath)); @@ -176,6 +186,7 @@ final class DCmdConfigure extends AbstractDCmd { if (!updated) { println("Current configuration:"); println(); + printPreserveRepository(); printRepositoryPath(); printDumpPath(); printStackDepth(); @@ -194,6 +205,10 @@ final class DCmdConfigure extends AbstractDCmd { println(); } + private void printPreserveRepository() { + println("Preserve repository: " + Options.getPreserveRepository()); + } + private void printDumpPath() { print("Dump path: "); printPath(Options.getDumpPath()); diff --git a/test/jdk/jdk/jfr/jcmd/TestJcmdPreserveRepository.java b/test/jdk/jdk/jfr/jcmd/TestJcmdPreserveRepository.java new file mode 100644 index 00000000000..f1550e633eb --- /dev/null +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdPreserveRepository.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, 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.jcmd; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import jdk.test.lib.jfr.FileHelper; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Utils; +/** + * @test + * @summary Test verifies that files are left after preserve-repository has been set using jcmd JFR.configure + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.jcmd.TestJcmdPreserveRepository + */ +public class TestJcmdPreserveRepository { + + public static class TestProcess { + public static void main(String... args) { + OutputAnalyzer output = JcmdHelper.jcmd("JFR.configure", "preserve-repository=true"); + System.exit(output.getExitValue()); + } + } + + public static void main(String[] args) throws Throwable { + Path path = Path.of("./preserved"); + String[] arguments = { + "-XX:StartFlightRecording", + "-XX:FlightRecorderOptions:repository=" + path, + "-Dtest.jdk=" + System.getProperty("test.jdk"), + TestProcess.class.getName() + }; + OutputAnalyzer output = ProcessTools.executeTestJvm(arguments); + output.shouldHaveExitValue(0); + Optional p = Files.find(path, 99, (a,b) -> a.getFileName().toString().endsWith(".jfr")).findAny(); + if (p.isEmpty()) { + throw new Exception("Could not find preserved files in repository"); + } + } + +} diff --git a/test/jdk/jdk/jfr/startupargs/TestPreserveRepository.java b/test/jdk/jdk/jfr/startupargs/TestPreserveRepository.java new file mode 100644 index 00000000000..335681bfc0c --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/TestPreserveRepository.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, 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.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +/** + * @test + * @summary Tests that -XX:FlightRecorderOptions:preserve-repository works + * @key jfr + * @requires vm.hasJFR + * @modules jdk.jfr + * @library /test/lib + * @run main/othervm jdk.jfr.startupargs.TestPreserveRepository + */ +public class TestPreserveRepository { + + public static void main(String... args) throws Exception { + Path path = Path.of("./preserved"); + String[] arguments = { + "-XX:StartFlightRecording", + "-XX:FlightRecorderOptions:repository=" + path + ",preserve-repository=true", + "-version" + }; + ProcessBuilder pb = ProcessTools.createTestJvm(arguments); + OutputAnalyzer output = ProcessTools.executeProcess(pb); + output.shouldHaveExitValue(0); + Optional p = Files.find(path, 99, (a,b) -> a.getFileName().toString().endsWith(".jfr")).findAny(); + if (p.isEmpty()) { + throw new Exception("Could not find preserved files in repository"); + } + } +}