diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index ba11d78060a..612ce2c3fa1 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -82,7 +82,11 @@ "be dumped into the corefile.") \ \ diagnostic(bool, UseCpuAllocPath, false, \ - "Use CPU_ALLOC code path in os::active_processor_count ") + "Use CPU_ALLOC code path in os::active_processor_count ") \ + \ + diagnostic(bool, DumpPerfMapAtExit, false, \ + "Write map file for Linux perf tool at exit") + // // Defines Linux-specific default values. The flags are available on all diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 888f80a2ad5..ed4122789a8 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -5623,6 +5623,12 @@ jint os::init_2(void) { set_coredump_filter(FILE_BACKED_SHARED_BIT); } + if (DumpPerfMapAtExit && FLAG_IS_DEFAULT(UseCodeCacheFlushing)) { + // Disable code cache flushing to ensure the map file written at + // exit contains all nmethods generated during execution. + FLAG_SET_DEFAULT(UseCodeCacheFlushing, false); + } + return JNI_OK; } diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index e26473e1494..770529a2420 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -1716,6 +1716,38 @@ void CodeCache::log_state(outputStream* st) { unallocated_capacity()); } +#ifdef LINUX +void CodeCache::write_perf_map() { + MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); + + // Perf expects to find the map file at /tmp/perf-.map. + char fname[32]; + jio_snprintf(fname, sizeof(fname), "/tmp/perf-%d.map", os::current_process_id()); + + fileStream fs(fname, "w"); + if (!fs.is_open()) { + log_warning(codecache)("Failed to create %s for perf map", fname); + return; + } + + // In latest hotspot, code cache sweeper supports unloading nmethods concurrently with GC thread. + // One nmethod marked as unloading will be unloaded in the future but still keeps alive at present. + // Here sweeper hasn't supported concurrent unloading yet and no nmethods are in unloading state, + // therefore we only iterate live code blobs. + LiveCodeBlobsIterator iter; + while (iter.next()) { + CodeBlob *cb = iter.method(); + ResourceMark rm; + const char* method_name = + cb->is_compiled() ? cb->as_compiled_method()->method()->external_name() + : cb->name(); + fs.print_cr(INTPTR_FORMAT " " INTPTR_FORMAT " %s", + (intptr_t)cb->code_begin(), (intptr_t)cb->code_size(), + method_name); + } +} +#endif // LINUX + //---< BEGIN >--- CodeHeap State Analytics. void CodeCache::aggregate(outputStream *out, size_t granularity) { diff --git a/src/hotspot/share/code/codeCache.hpp b/src/hotspot/share/code/codeCache.hpp index e79aad33636..cd82457a455 100644 --- a/src/hotspot/share/code/codeCache.hpp +++ b/src/hotspot/share/code/codeCache.hpp @@ -205,6 +205,7 @@ class CodeCache : AllStatic { static void print_trace(const char* event, CodeBlob* cb, int size = 0) PRODUCT_RETURN; static void print_summary(outputStream* st, bool detailed = true); // Prints a summary of the code cache usage static void log_state(outputStream* st); + LINUX_ONLY(static void write_perf_map();) static const char* get_code_heap_name(int code_blob_type) { return (heap_available(code_blob_type) ? get_code_heap(code_blob_type)->name() : "Unused"); } static void report_codemem_full(int code_blob_type, bool print); @@ -413,8 +414,13 @@ struct NMethodFilter { static const GrowableArray* heaps() { return CodeCache::nmethod_heaps(); } }; +struct LiveCodeBlobFilter { + static bool apply(CodeBlob* cb) { return cb->is_alive(); } + static const GrowableArray* heaps() { return CodeCache::heaps(); } +}; typedef CodeBlobIterator CompiledMethodIterator; typedef CodeBlobIterator NMethodIterator; +typedef CodeBlobIterator LiveCodeBlobsIterator; #endif // SHARE_VM_CODE_CODECACHE_HPP diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 84123b29ecd..1100512c991 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -527,6 +527,12 @@ void before_exit(JavaThread* thread) { BytecodeHistogram::print(); } +#ifdef LINUX + if (DumpPerfMapAtExit) { + CodeCache::write_perf_map(); + } +#endif + if (JvmtiExport::should_post_thread_life()) { JvmtiExport::post_thread_end(thread); } diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index f12e5c72942..ebcace1a214 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -117,6 +117,7 @@ void DCmdRegistrant::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #ifdef LINUX DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // LINUX DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); @@ -995,6 +996,12 @@ void CodeCacheDCmd::execute(DCmdSource source, TRAPS) { CodeCache::print_layout(output()); } +#ifdef LINUX +void PerfMapDCmd::execute(DCmdSource source, TRAPS) { + CodeCache::write_perf_map(); +} +#endif // LINUX + //---< BEGIN >--- CodeHeap State Analytics. CodeHeapAnalyticsDCmd::CodeHeapAnalyticsDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 12d46a8153f..a0ecc89ef7d 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -603,6 +603,29 @@ class CompileQueueDCmd : public DCmd { virtual void execute(DCmdSource source, TRAPS); }; +#ifdef LINUX +class PerfMapDCmd : public DCmd { +public: + PerfMapDCmd(outputStream* output, bool heap) : DCmd(output, heap) {} + static const char* name() { + return "Compiler.perfmap"; + } + static const char* description() { + return "Write map file for Linux perf tool."; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", NULL}; + return p; + } + static int num_arguments() { return 0; } + virtual void execute(DCmdSource source, TRAPS); +}; +#endif // LINUX + class CodeListDCmd : public DCmd { public: CodeListDCmd(outputStream* output, bool heap) : DCmd(output, heap) {} @@ -624,7 +647,6 @@ class CodeListDCmd : public DCmd { virtual void execute(DCmdSource source, TRAPS); }; - class CodeCacheDCmd : public DCmd { public: CodeCacheDCmd(outputStream* output, bool heap) : DCmd(output, heap) {} diff --git a/test/hotspot/jtreg/serviceability/dcmd/compiler/PerfMapTest.java b/test/hotspot/jtreg/serviceability/dcmd/compiler/PerfMapTest.java new file mode 100644 index 00000000000..f4da5e979e3 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/compiler/PerfMapTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Arm Limited. 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. + */ + +/* + * @test PerfMapTest + * @bug 8254723 + * @requires os.family == "linux" + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run testng/othervm PerfMapTest + * @summary Test of diagnostic command Compiler.perfmap + */ + +import org.testng.annotations.Test; +import org.testng.Assert; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Call jcmd Compiler.perfmap and check the output file has the expected + * format. + */ +public class PerfMapTest { + + static final Pattern LINE_PATTERN = + Pattern.compile("^((?:0x)?\\p{XDigit}+)\\s+((?:0x)?\\p{XDigit}+)\\s+(.*)$"); + + public void run(CommandExecutor executor) { + OutputAnalyzer output = executor.execute("Compiler.perfmap"); + + output.stderrShouldBeEmpty(); + output.stdoutShouldBeEmpty(); + + final long pid = ProcessHandle.current().pid(); + final Path path = Paths.get(String.format("/tmp/perf-%d.map", pid)); + + Assert.assertTrue(Files.exists(path)); + + // Sanity check the file contents + try { + for (String entry : Files.readAllLines(path)) { + Matcher m = LINE_PATTERN.matcher(entry); + Assert.assertTrue(m.matches(), "Invalid file format: " + entry); + } + } catch (IOException e) { + Assert.fail(e.toString()); + } + } + + @Test + public void jmx() { + run(new JMXExecutor()); + } +}