From 6f863b2a1baa67deb2a7b33fcd93d272aea01812 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Mon, 13 Nov 2023 08:26:42 +0000 Subject: [PATCH] 8318636: Add jcmd to print annotated process memory map Reviewed-by: jsjolen, gziemski --- src/hotspot/os/linux/memMapPrinter_linux.cpp | 84 +++++ src/hotspot/share/logging/logAsyncWriter.hpp | 3 +- src/hotspot/share/nmt/memFlagBitmap.hpp | 54 +++ src/hotspot/share/nmt/memMapPrinter.cpp | 309 ++++++++++++++++++ src/hotspot/share/nmt/memMapPrinter.hpp | 75 +++++ .../share/services/diagnosticCommand.cpp | 48 +++ .../share/services/diagnosticCommand.hpp | 41 +++ .../dcmd/vm/SystemDumpMapTest.java | 111 +++++++ .../serviceability/dcmd/vm/SystemMapTest.java | 68 ++++ 9 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 src/hotspot/os/linux/memMapPrinter_linux.cpp create mode 100644 src/hotspot/share/nmt/memFlagBitmap.hpp create mode 100644 src/hotspot/share/nmt/memMapPrinter.cpp create mode 100644 src/hotspot/share/nmt/memMapPrinter.hpp create mode 100644 test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java create mode 100644 test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java diff --git a/src/hotspot/os/linux/memMapPrinter_linux.cpp b/src/hotspot/os/linux/memMapPrinter_linux.cpp new file mode 100644 index 00000000000..05d520f6953 --- /dev/null +++ b/src/hotspot/os/linux/memMapPrinter_linux.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * 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 "runtime/os.hpp" +#include "nmt/memMapPrinter.hpp" +#include "utilities/globalDefinitions.hpp" +#include + +struct ProcMapsInfo { + void* from = 0; + void* to = 0; + char prot[20 + 1]; + char offset[20 + 1]; + char dev[20 + 1]; + char inode[20 + 1]; + char filename[1024 + 1]; + + bool scan_proc_maps_line(const char* line) { + prot[0] = offset[0] = dev[0] = inode[0] = filename[0] = '\0'; + const int items_read = ::sscanf(line, "%p-%p %20s %20s %20s %20s %1024s", + &from, &to, prot, offset, dev, inode, filename); + return items_read >= 2; // need at least from and to + } +}; + +class LinuxMappingPrintInformation : public MappingPrintInformation { + const ProcMapsInfo _info; +public: + + LinuxMappingPrintInformation(const void* from, const void* to, const ProcMapsInfo* info) : + MappingPrintInformation(from, to), _info(*info) {} + + void print_OS_specific_details(outputStream* st) const override { + st->print("%s %s ", _info.prot, _info.offset); + } + + const char* filename() const override { return _info.filename; } +}; + +void MemMapPrinter::pd_print_header(outputStream* st) { + st->print_cr("size prot offset What"); +} + +void MemMapPrinter::pd_iterate_all_mappings(MappingPrintClosure& closure) { + FILE* f = os::fopen("/proc/self/maps", "r"); + if (f == nullptr) { + return; + } + constexpr size_t linesize = sizeof(ProcMapsInfo); + char line[linesize]; + while (fgets(line, sizeof(line), f) == line) { + line[sizeof(line) - 1] = '\0'; + ProcMapsInfo info; + if (info.scan_proc_maps_line(line)) { + LinuxMappingPrintInformation mapinfo(info.from, info.to, &info); + closure.do_it(&mapinfo); + } + } + ::fclose(f); +} diff --git a/src/hotspot/share/logging/logAsyncWriter.hpp b/src/hotspot/share/logging/logAsyncWriter.hpp index 5015ddfe1a9..97bae2a5817 100644 --- a/src/hotspot/share/logging/logAsyncWriter.hpp +++ b/src/hotspot/share/logging/logAsyncWriter.hpp @@ -178,7 +178,6 @@ class AsyncLogWriter : public NonJavaThread { NonJavaThread::pre_run(); log_debug(logging, thread)("starting AsyncLog Thread tid = " INTX_FORMAT, os::current_thread_id()); } - const char* name() const override { return "AsyncLog Thread"; } const char* type_name() const override { return "AsyncLogWriter"; } void print_on(outputStream* st) const override { st->print("\"%s\" ", name()); @@ -203,6 +202,8 @@ class AsyncLogWriter : public NonJavaThread { static AsyncLogWriter* instance(); static void initialize(); static void flush(); + + const char* name() const override { return "AsyncLog Thread"; } }; #endif // SHARE_LOGGING_LOGASYNCWRITER_HPP diff --git a/src/hotspot/share/nmt/memFlagBitmap.hpp b/src/hotspot/share/nmt/memFlagBitmap.hpp new file mode 100644 index 00000000000..87815536980 --- /dev/null +++ b/src/hotspot/share/nmt/memFlagBitmap.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, 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. + * + */ + +#ifndef SHARE_NMT_MEMFLAGBITMAP_HPP +#define SHARE_NMT_MEMFLAGBITMAP_HPP + +#include "memory/allocation.hpp" // for mt_number_of_types +#include "utilities/globalDefinitions.hpp" + +class MemFlagBitmap { + uint32_t _v; + STATIC_ASSERT(sizeof(_v) * BitsPerByte >= mt_number_of_types); + +public: + MemFlagBitmap(uint32_t v = 0) : _v(v) {} + MemFlagBitmap(const MemFlagBitmap& o) : _v(o._v) {} + + uint32_t raw_value() const { return _v; } + + void set_flag(MEMFLAGS f) { + const int bitno = (int)f; + _v |= nth_bit(bitno); + } + + bool has_flag(MEMFLAGS f) const { + const int bitno = (int)f; + return _v & nth_bit(bitno); + } + + bool has_any() const { return _v > 0; } +}; + +#endif // SHARE_NMT_NMTUSAGE_HPP diff --git a/src/hotspot/share/nmt/memMapPrinter.cpp b/src/hotspot/share/nmt/memMapPrinter.cpp new file mode 100644 index 00000000000..a112ae6f862 --- /dev/null +++ b/src/hotspot/share/nmt/memMapPrinter.cpp @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * 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" + +#ifdef LINUX + +#include "logging/logAsyncWriter.hpp" +#include "gc/shared/collectedHeap.hpp" +#include "memory/allocation.hpp" +#include "memory/universe.hpp" +#include "runtime/nonJavaThread.hpp" +#include "runtime/osThread.hpp" +#include "runtime/thread.hpp" +#include "runtime/threadSMR.hpp" +#include "runtime/vmThread.hpp" +#include "nmt/memFlagBitmap.hpp" +#include "nmt/memMapPrinter.hpp" +#include "nmt/memTracker.hpp" +#include "nmt/virtualMemoryTracker.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/ostream.hpp" + +// Note: throughout this code we will use the term "VMA" for OS system level memory mapping + +/// NMT mechanics + +// Short, clear, descriptive names for all possible markers. Note that we only expect to see +// those that have been used with mmap. Flags left out are printed with their nmt flag name. +#define NMT_FLAGS_DO(f) \ + /* flag, short, description */ \ + f(mtGCCardSet, "CARDTBL", "GC Card table") \ + f(mtClassShared, "CDS", "CDS archives") \ + f(mtClass, "CLASS", "Class Space") \ + f(mtCode, "CODE", "Code Heap") \ + f(mtGC, "GC", "GC support data (e.g. bitmaps)") \ + f(mtInternal, "INTERN", "Internal") \ + f(mtJavaHeap, "JAVAHEAP", "Java Heap") \ + f(mtOther, "JDK", "allocated by JDK libraries other than VM") \ + f(mtMetaspace, "META", "Metaspace nodes (non-class)") \ + f(mtSafepoint, "POLL", "Polling pages") \ + f(mtThreadStack, "STACK", "(known) Thread Stack") \ + f(mtTest, "TEST", "JVM internal test mappings") + //end + +static const char* get_shortname_for_nmt_flag(MEMFLAGS f) { +#define DO(flag, shortname, text) if (flag == f) return shortname; + NMT_FLAGS_DO(DO) +#undef DO + return NMTUtil::flag_to_enum_name(f); +} + +/// NMT virtual memory + +static bool range_intersects(const void* from1, const void* to1, const void* from2, const void* to2) { + return MAX2(from1, from2) < MIN2(to1, to2); +} + +// A Cache that correlates range with MEMFLAG, optimized to be iterated quickly +// (cache friendly). +class CachedNMTInformation : public VirtualMemoryWalker { + struct Range { const void* from; const void* to; }; + // We keep ranges apart from flags since that prevents the padding a combined + // structure would have, and it allows for faster iteration of ranges since more + // of them fit into a cache line. + Range* _ranges; + MEMFLAGS* _flags; + uintx _count, _capacity; +public: + CachedNMTInformation() : _ranges(nullptr), _flags(nullptr), _count(0), _capacity(0) {} + + ~CachedNMTInformation() { + ALLOW_C_FUNCTION(free, ::free(_ranges);) + ALLOW_C_FUNCTION(free, ::free(_flags);) + } + + bool add(const void* from, const void* to, MEMFLAGS f) { + // We rely on NMT regions being sorted by base + assert(_count == 0 || (from >= _ranges[_count - 1].to), "NMT regions unordered?"); + // we can just fold two regions if they are adjacent and have the same flag. + if (_count > 0 && from == _ranges[_count - 1].to && f == _flags[_count - 1]) { + _ranges[_count - 1].to = to; + return true; + } + if (_count == _capacity) { + // Enlarge if needed + const uintx new_capacity = MAX2((uintx)4096, 2 * _capacity); + // Unfortunately, we need to allocate manually, raw, since we must prevent NMT deadlocks (ThreadCritical). + ALLOW_C_FUNCTION(realloc, _ranges = (Range*)::realloc(_ranges, new_capacity * sizeof(Range));) + ALLOW_C_FUNCTION(realloc, _flags = (MEMFLAGS*)::realloc(_flags, new_capacity * sizeof(MEMFLAGS));) + if (_ranges == nullptr || _flags == nullptr) { + // In case of OOM lets make no fuzz. Just return. + return false; + } + _capacity = new_capacity; + } + assert(_capacity > _count, "Sanity"); + _ranges[_count] = Range { from, to }; + _flags[_count] = f; + _count++; + return true; + } + + // Given a vma [from, to), find all regions that intersect with this vma and + // return their collective flags. + MemFlagBitmap lookup(const void* from, const void* to) const { + MemFlagBitmap bm; + for(uintx i = 0; i < _count; i++) { + if (range_intersects(from, to, _ranges[i].from, _ranges[i].to)) { + bm.set_flag(_flags[i]); + } else if (from < _ranges[i].to) { + break; + } + } + return bm; + } + + bool do_allocation_site(const ReservedMemoryRegion* rgn) override { + // Cancel iteration if we run out of memory (add returns false); + return add(rgn->base(), rgn->end(), rgn->flag()); + } + + // Iterate all NMT virtual memory regions and fill this cache. + bool fill_from_nmt() { + return VirtualMemoryTracker::walk_virtual_memory(this); + } +}; + +/////// Thread information ////////////////////////// + +// Given a VMA [from, to) and a thread, check if vma intersects with thread stack +static bool vma_touches_thread_stack(const void* from, const void* to, const Thread* t) { + // Java thread stacks (and sometimes also other threads) have guard pages. Therefore they typically occupy + // at least two distinct neighboring VMAs. Therefore we typically have a 1:n relationshipt between thread + // stack and vma. + // Very rarely however is a VMA backing a thread stack folded together with another adjacent VMA by the + // kernel. That can happen, e.g., for non-java threads that don't have guard pages. + // Therefore we go for the simplest way here and check for intersection between VMA and thread stack. + return range_intersects(from, to, (const void*)t->stack_end(), (const void*)t->stack_base()); +} + +struct GCThreadClosure : public ThreadClosure { + bool _found; + uintx _tid; + const void* const _from; + const void* const _to; + GCThreadClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {} + void do_thread(Thread* t) override { + if (_tid == 0 && t != nullptr && vma_touches_thread_stack(_from, _to, t)) { + _found = true; + _tid = t->osthread()->thread_id(); + // lemme stooop! No way to signal stop :( + } + } +}; + +static void print_thread_details(uintx thread_id, const char* name, outputStream* st) { + st->print("(" UINTX_FORMAT " \"%s\")", (uintx)thread_id, name); +} + +// Given a region [from, to), if it intersects a known thread stack, print detail infos about that thread. +static void print_thread_details_for_supposed_stack_address(const void* from, const void* to, outputStream* st) { + +#define HANDLE_THREAD(T) \ + if (T != nullptr && vma_touches_thread_stack(from, to, T)) { \ + print_thread_details((uintx)(T->osthread()->thread_id()), T->name(), st); \ + return; \ + } + for (JavaThreadIteratorWithHandle jtiwh; JavaThread* t = jtiwh.next(); ) { + HANDLE_THREAD(t); + } + HANDLE_THREAD(VMThread::vm_thread()); + HANDLE_THREAD(WatcherThread::watcher_thread()); + HANDLE_THREAD(AsyncLogWriter::instance()); +#undef HANDLE_THREAD + + if (Universe::heap() != nullptr) { + GCThreadClosure cl(from, to); + Universe::heap()->gc_threads_do(&cl); + if (cl._found) { + print_thread_details(cl._tid, "GC Thread", st); + } + } +} + +/////////////// + +static void print_legend(outputStream* st) { +#define DO(flag, shortname, text) st->print_cr("%10s %s", shortname, text); + NMT_FLAGS_DO(DO) +#undef DO +} + +MappingPrintClosure::MappingPrintClosure(outputStream* st, bool human_readable, const CachedNMTInformation& nmt_info) : + _out(st), _human_readable(human_readable), + _total_count(0), _total_vsize(0), _nmt_info(nmt_info) +{} + +void MappingPrintClosure::do_it(const MappingPrintInformation* info) { + + _total_count++; + + const void* const vma_from = info->from(); + const void* const vma_to = info->to(); + + // print from, to + _out->print(PTR_FORMAT " - " PTR_FORMAT " ", p2i(vma_from), p2i(vma_to)); + const size_t size = pointer_delta(vma_to, vma_from, 1); + _total_vsize += size; + + // print mapping size + if (_human_readable) { + _out->print(PROPERFMT " ", PROPERFMTARGS(size)); + } else { + _out->print("%11zu", size); + } + + assert(info->from() <= info->to(), "Invalid VMA"); + _out->fill_to(53); + info->print_OS_specific_details(_out); + _out->fill_to(70); + + // print NMT information, if available + if (MemTracker::enabled()) { + // Correlate vma region (from, to) with NMT region(s) we collected previously. + const MemFlagBitmap flags = _nmt_info.lookup(vma_from, vma_to); + if (flags.has_any()) { + for (int i = 0; i < mt_number_of_types; i++) { + const MEMFLAGS flag = (MEMFLAGS)i; + if (flags.has_flag(flag)) { + _out->print("%s", get_shortname_for_nmt_flag(flag)); + if (flag == mtThreadStack) { + print_thread_details_for_supposed_stack_address(vma_from, vma_to, _out); + } + _out->print(" "); + } + } + } + } + + // print file name, if available + const char* f = info->filename(); + if (f != nullptr) { + _out->print_raw(f); + } + _out->cr(); +} + +void MemMapPrinter::print_header(outputStream* st) { + st->print( +#ifdef _LP64 + // 0x0000000000000000 - 0x0000000000000000 + "from to " +#else + // 0x00000000 - 0x00000000 + "from to " +#endif + ); + // Print platform-specific columns + pd_print_header(st); +} + +void MemMapPrinter::print_all_mappings(outputStream* st, bool human_readable) { + // First collect all NMT information + CachedNMTInformation nmt_info; + nmt_info.fill_from_nmt(); + + st->print_cr("Memory mappings:"); + if (!MemTracker::enabled()) { + st->cr(); + st->print_cr(" (NMT is disabled, will not annotate mappings)."); + } + st->cr(); + + print_legend(st); + st->print_cr("(*) - Mapping contains data from multiple regions"); + st->cr(); + + pd_print_header(st); + MappingPrintClosure closure(st, human_readable, nmt_info); + pd_iterate_all_mappings(closure); + st->print_cr("Total: " UINTX_FORMAT " mappings with a total vsize of %zu (" PROPERFMT ")", + closure.total_count(), closure.total_vsize(), PROPERFMTARGS(closure.total_vsize())); +} + +#endif // LINUX diff --git a/src/hotspot/share/nmt/memMapPrinter.hpp b/src/hotspot/share/nmt/memMapPrinter.hpp new file mode 100644 index 00000000000..09e1cea113b --- /dev/null +++ b/src/hotspot/share/nmt/memMapPrinter.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * 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. + * + */ + +#ifndef SHARE_SERVICES_MEMMAPPRINTER_HPP +#define SHARE_SERVICES_MEMMAPPRINTER_HPP + +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef LINUX + +class outputStream; +class CachedNMTInformation; + +class MappingPrintInformation { + const void* const _from; + const void* const _to; +public: + MappingPrintInformation(const void* from, const void* to) : _from(from), _to(to) {} + const void* from() const { return _from; } + const void* to() const { return _to; } + // Will be called for each mapping before VM annotations are printed. + virtual void print_OS_specific_details(outputStream* st) const {} + // If mapping is backed by a file, the name of that file + virtual const char* filename() const { return nullptr; } +}; + +class MappingPrintClosure { + outputStream* const _out; + const bool _human_readable; + uintx _total_count; + size_t _total_vsize; + const CachedNMTInformation& _nmt_info; +public: + MappingPrintClosure(outputStream* st, bool human_readable, const CachedNMTInformation& nmt_info); + void do_it(const MappingPrintInformation* info); // returns false if timeout reached. + uintx total_count() const { return _total_count; } + size_t total_vsize() const { return _total_vsize; } +}; + +class MemMapPrinter : public AllStatic { + static void pd_print_header(outputStream* st); + static void print_header(outputStream* st); + static void pd_iterate_all_mappings(MappingPrintClosure& closure); +public: + static void mark_page_malloced(const void* p, MEMFLAGS f); + static void print_all_mappings(outputStream* st, bool human_readable); +}; + +#endif // LINUX + +#endif // SHARE_SERVICES_MEMMAPPRINTER_HPP diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 98bfcbafd29..bf9f9f02573 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -40,6 +40,8 @@ #include "memory/metaspace/metaspaceDCmd.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "nmt/memMapPrinter.hpp" +#include "nmt/memTracker.hpp" #include "nmt/nmtDCmd.hpp" #include "oops/instanceKlass.hpp" #include "oops/objArrayOop.inline.hpp" @@ -67,8 +69,10 @@ #include "utilities/macros.hpp" #include "utilities/parseInteger.hpp" #ifdef LINUX +#include "os_posix.hpp" #include "mallocInfoDcmd.hpp" #include "trimCHeapDCmd.hpp" +#include #endif static void loadAgentModule(TRAPS) { @@ -133,6 +137,8 @@ void DCmd::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + 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)); @@ -1151,3 +1157,45 @@ void CompilationMemoryStatisticDCmd::execute(DCmdSource source, TRAPS) { const size_t minsize = _minsize.has_value() ? _minsize.value()._size : 0; CompilationMemoryStatistic::print_all_by_size(output(), human_readable, minsize); } + +#ifdef LINUX + +SystemMapDCmd::SystemMapDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _human_readable("-H", "Human readable format", "BOOLEAN", false, "false") { + _dcmdparser.add_dcmd_option(&_human_readable); +} + +void SystemMapDCmd::execute(DCmdSource source, TRAPS) { + MemMapPrinter::print_all_mappings(output(), _human_readable.value()); +} + +SystemDumpMapDCmd::SystemDumpMapDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _human_readable("-H", "Human readable format", "BOOLEAN", false, "false"), + _filename("-F", "file path (defaults: \"vm_memory_map_.txt\")", "STRING", false) { + _dcmdparser.add_dcmd_option(&_human_readable); + _dcmdparser.add_dcmd_option(&_filename); +} + +void SystemDumpMapDCmd::execute(DCmdSource source, TRAPS) { + stringStream default_name; + default_name.print("vm_memory_map_%d.txt", os::current_process_id()); + const char* name = _filename.is_set() ? _filename.value() : default_name.base(); + fileStream fs(name); + if (fs.is_open()) { + if (!MemTracker::enabled()) { + output()->print_cr("(NMT is disabled, will not annotate mappings)."); + } + MemMapPrinter::print_all_mappings(&fs, _human_readable.value()); + // For the readers convenience, resolve path name. + char tmp[JVM_MAXPATHLEN]; + const char* absname = os::Posix::realpath(name, tmp, sizeof(tmp)); + name = absname != nullptr ? absname : name; + output()->print_cr("Memory map dumped to \"%s\".", name); + } else { + output()->print_cr("Failed to open \"%s\" for writing (%s).", name, os::strerror(errno)); + } +} + +#endif // LINUX diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 06b7e8748dc..395212a204e 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -978,4 +978,45 @@ public: virtual void execute(DCmdSource source, TRAPS); }; +#ifdef LINUX + +class SystemMapDCmd : public DCmdWithParser { + DCmdArgument _human_readable; +public: + static int num_arguments() { return 1; } + SystemMapDCmd(outputStream* output, bool heap); + static const char* name() { return "System.map"; } + static const char* description() { + return "Prints an annotated process memory map of the VM process (linux only)."; + } + static const char* impact() { return "Low"; } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "control", nullptr}; + return p; + } + virtual void execute(DCmdSource source, TRAPS); +}; + +class SystemDumpMapDCmd : public DCmdWithParser { + DCmdArgument _human_readable; + DCmdArgument _filename; +public: + static int num_arguments() { return 2; } + SystemDumpMapDCmd(outputStream* output, bool heap); + static const char* name() { return "System.dump_map"; } + static const char* description() { + return "Dumps an annotated process memory map to an output file (linux only)."; + } + static const char* impact() { return "Low"; } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "control", nullptr}; + return p; + } + virtual void execute(DCmdSource source, TRAPS); +}; + +#endif // LINUX + #endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java new file mode 100644 index 00000000000..347e8884096 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * 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. + */ + +import org.testng.annotations.Test; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.regex.Pattern; + +/* + * @test + * @summary Test of diagnostic command System.map + * @library /test/lib + * @requires (os.family=="linux") + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run testng SystemDumpMapTest + */ +public class SystemDumpMapTest { + + private void run_test(CommandExecutor executor, boolean useDefaultFileName) { + + String filenameOption = useDefaultFileName ? "" : "-F=test-map.txt"; + + OutputAnalyzer output = executor.execute("System.dump_map " + filenameOption); + output.reportDiagnosticSummary(); + + String filename = useDefaultFileName ? + output.firstMatch("Memory map dumped to \"(\\S*vm_memory_map_\\d+\\.txt)\".*", 1) : + output.firstMatch("Memory map dumped to \"(\\S*test-map.txt)\".*", 1); + + if (filename == null) { + throw new RuntimeException("Did not find dump file in output.\n"); + } + + try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { + boolean NMTOff = output.contains("NMT is disabled"); + String regexBase = ".*0x\\p{XDigit}+ - 0x\\p{XDigit}+ +\\d+"; + HashSet patterns = new HashSet<>(); + patterns.add(Pattern.compile(regexBase + ".*jvm.*")); + if (!NMTOff) { // expect VM annotations if NMT is on + patterns.add(Pattern.compile(regexBase + ".*JAVAHEAP.*")); + patterns.add(Pattern.compile(regexBase + ".*META.*")); + patterns.add(Pattern.compile(regexBase + ".*CODE.*")); + patterns.add(Pattern.compile(regexBase + ".*STACK.*main.*")); + } + do { + String line = reader.readLine(); + if (line != null) { + for (Pattern pat : patterns) { + if (pat.matcher(line).matches()) { + patterns.remove(pat); + break; + } + } + } else { + break; + } + } while (patterns.size() > 0); + + if (patterns.size() > 0) { + System.out.println("Missing patterns in dump:"); + for (Pattern pat : patterns) { + System.out.println(pat); + } + throw new RuntimeException("Missing patterns"); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public void run(CommandExecutor executor) { + run_test(executor, false); + run_test(executor, true); + } + + @Test + public void jmx() { + run(new JMXExecutor()); + } +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java new file mode 100644 index 00000000000..9fb8efe5a71 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * 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. + */ + +import org.testng.annotations.Test; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.*; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.regex.Pattern; + +/* + * @test + * @summary Test of diagnostic command System.map + * @library /test/lib + * @requires (os.family=="linux") + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run testng SystemMapTest + */ +public class SystemMapTest { + public void run(CommandExecutor executor) { + OutputAnalyzer output = executor.execute("System.map"); + output.reportDiagnosticSummary(); + boolean NMTOff = output.contains("NMT is disabled"); + + String regexBase = ".*0x\\p{XDigit}+ - 0x\\p{XDigit}+ +\\d+"; + output.shouldMatch(regexBase + ".*jvm.*"); + if (!NMTOff) { // expect VM annotations if NMT is on + output.shouldMatch(regexBase + ".*JAVAHEAP.*"); + output.shouldMatch(regexBase + ".*META.*"); + output.shouldMatch(regexBase + ".*CODE.*"); + output.shouldMatch(regexBase + ".*STACK.*main.*"); + } + } + + @Test + public void jmx() { + run(new JMXExecutor()); + } +}