diff --git a/src/hotspot/share/nmt/mallocLimit.cpp b/src/hotspot/share/nmt/mallocLimit.cpp index 1751cb59ae7f0..13446b29aa1a4 100644 --- a/src/hotspot/share/nmt/mallocLimit.cpp +++ b/src/hotspot/share/nmt/mallocLimit.cpp @@ -158,6 +158,23 @@ void MallocLimitSet::print_on(outputStream* st) const { } } +void MallocLimitSet::print_on_xml(xmlStream* xs) const { + if (_glob.sz > 0) { + XmlParent("globalLimit"); + XmlElem("amount", "%zu", _glob.sz); + XmlElem("name", "%s", mode_to_name(_glob.mode)); + } else { + XmlParent("memTagLimit"); + for (int i = 0; i < mt_number_of_tags; i++) { + if (_mtag[i].sz > 0) { + XmlElem("memTag", "%s", NMTUtil::tag_to_enum_name(NMTUtil::index_to_tag(i))); + XmlElem("amount", "%zu", _mtag[i].sz); + XmlElem("modeName", "%s", mode_to_name(_mtag[i].mode)); + } + } + } +} + bool MallocLimitSet::parse_malloclimit_option(const char* v, const char** err) { #define BAIL_UNLESS(condition, errormessage) if (!(condition)) { *err = errormessage; return false; } @@ -228,3 +245,12 @@ void MallocLimitHandler::print_on(outputStream* st) { st->print_cr("MallocLimit: unset"); } } + +void MallocLimitHandler::print_on_xml(xmlStream* xs) { + if (have_limit()) { + XmlParent("mallocLimits") + _limits.print_on_xml(xs); + } else { + XmlElem("mallocLimit", "unset"); + } +} diff --git a/src/hotspot/share/nmt/mallocLimit.hpp b/src/hotspot/share/nmt/mallocLimit.hpp index d41eaabea462d..6bd29fa335079 100644 --- a/src/hotspot/share/nmt/mallocLimit.hpp +++ b/src/hotspot/share/nmt/mallocLimit.hpp @@ -30,6 +30,7 @@ #include "nmt/memTag.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/xmlstream.hpp" enum class MallocLimitMode { trigger_fatal = 0, @@ -60,6 +61,7 @@ class MallocLimitSet { const malloclimit* mem_tag_limit(MemTag mem_tag) const { return &_mtag[(int)mem_tag]; } void print_on(outputStream* st) const; + void print_on_xml(xmlStream* st) const; }; class MallocLimitHandler : public AllStatic { @@ -73,6 +75,7 @@ class MallocLimitHandler : public AllStatic { static void initialize(const char* options); static void print_on(outputStream* st); + static void print_on_xml(xmlStream* st); // True if there is any limit established static bool have_limit() { return _have_limit; } diff --git a/src/hotspot/share/nmt/mallocSiteTable.cpp b/src/hotspot/share/nmt/mallocSiteTable.cpp index c9ddffce5ecb7..7a99f165440f9 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.cpp +++ b/src/hotspot/share/nmt/mallocSiteTable.cpp @@ -249,6 +249,61 @@ void MallocSiteTable::print_tuning_statistics(outputStream* st) { st->cr(); } +void MallocSiteTable::print_tuning_statistics_xml(xmlStream* xs) { + // Total number of allocation sites, include empty sites + int total_entries = 0; + // Number of allocation sites that have all memory freed + int empty_entries = 0; + // Number of captured call stack distribution + int stack_depth_distribution[NMT_TrackingStackDepth + 1] = { 0 }; + // Chain lengths + uint16_t lengths[table_size] = { 0 }; + // Unused buckets + int unused_buckets = 0; + + for (int i = 0; i < table_size; i ++) { + int this_chain_length = 0; + const MallocSiteHashtableEntry* head = _table[i]; + if (head == nullptr) { + unused_buckets ++; + } + while (head != nullptr) { + total_entries ++; + this_chain_length ++; + if (head->size() == 0) { + empty_entries ++; + } + const int callstack_depth = head->peek()->call_stack()->frames(); + assert(callstack_depth >= 0 && callstack_depth <= NMT_TrackingStackDepth, + "Sanity (%d)", callstack_depth); + stack_depth_distribution[callstack_depth] ++; + head = head->next(); + } + lengths[i] = (uint16_t)MIN2(this_chain_length, USHRT_MAX); + } + XmlElem("totalEntries", "%d", total_entries); + XmlElem("emptyEntries", "%d", empty_entries); + XmlElem("emptyEntriesPercentage", "%2.2f", ((float)empty_entries * 100) / (float)total_entries); + + + qsort(lengths, table_size, sizeof(uint16_t), qsort_helper); + + { + XmlParent("bucketChainLengthDistribution"); + XmlElem("unused", "%d", unused_buckets); + XmlElem("longest", "%d", lengths[table_size - 1]); + XmlElem("median", "%d", lengths[table_size / 2]); + } + + { + XmlParent("callStackDepthDistribution"); + for (int i = 0; i <= NMT_TrackingStackDepth; i ++) { + XmlElem("depth", "%d", i); + XmlElem("count", "%d", stack_depth_distribution[i]); + } + } +} + bool MallocSiteHashtableEntry::atomic_insert(MallocSiteHashtableEntry* entry) { return AtomicAccess::replace_if_null(&_next, entry); } diff --git a/src/hotspot/share/nmt/mallocSiteTable.hpp b/src/hotspot/share/nmt/mallocSiteTable.hpp index e4adff9cc50c5..696a2cf99743c 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.hpp +++ b/src/hotspot/share/nmt/mallocSiteTable.hpp @@ -32,6 +32,7 @@ #include "runtime/atomicAccess.hpp" #include "utilities/macros.hpp" #include "utilities/nativeCallStack.hpp" +#include "utilities/xmlstream.hpp" // MallocSite represents a code path that eventually calls // os::malloc() to allocate memory @@ -168,6 +169,7 @@ class MallocSiteTable : AllStatic { static bool walk_malloc_site(MallocSiteWalker* walker); static void print_tuning_statistics(outputStream* st); + static void print_tuning_statistics_xml(xmlStream* st); private: static MallocSiteHashtableEntry* new_entry(const NativeCallStack& key, MemTag mem_tag); diff --git a/src/hotspot/share/nmt/memReporter.cpp b/src/hotspot/share/nmt/memReporter.cpp index 65d4d76942baa..8beaf6f3c7370 100644 --- a/src/hotspot/share/nmt/memReporter.cpp +++ b/src/hotspot/share/nmt/memReporter.cpp @@ -36,6 +36,7 @@ #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" +#include "utilities/xmlstream.hpp" #define INDENT_BY(num_chars, CODE) { \ StreamIndentor si(out, num_chars); \ @@ -143,7 +144,6 @@ void MemReporterBase::print_virtual_memory_region(const char* type, address base p2i(base), p2i(base + size), type, amount_in_current_scale(size), scale); } - void MemSummaryReporter::report() { outputStream* out = output(); const size_t total_malloced_bytes = _malloc_snapshot->total(); @@ -189,7 +189,6 @@ void MemSummaryReporter::report() { report_summary_of_tag(mem_tag, malloc_memory, virtual_memory); } } - void MemSummaryReporter::report_summary_of_tag(MemTag mem_tag, MallocMemory* malloc_memory, VirtualMemory* virtual_memory) { @@ -484,7 +483,7 @@ void MemDetailReporter::report_memory_file_allocations() { output()->print_raw(st.freeze()); } -void MemSummaryDiffReporter::report_diff() { +void MemSummaryDiffReporter::report_diff() const { outputStream* out = output(); out->cr(); out->print_cr("Native Memory Tracking:"); @@ -801,7 +800,7 @@ void MemSummaryDiffReporter::print_metaspace_diff(const char* header, out->print_cr(")"); } -void MemDetailDiffReporter::report_diff() { +void MemDetailDiffReporter::report_diff() const{ MemSummaryDiffReporter::report_diff(); diff_malloc_sites(); diff_virtual_memory_sites(); @@ -957,3 +956,849 @@ void MemDetailDiffReporter::diff_virtual_memory_site(const NativeCallStack* stac out->cr(); } +#define XmlParentElement(txt) XmlElemHelper _not_used(xml_output(), txt) +#define XmlStackElement XmlElemStack __not_used(xml_output(), "stack") +#define XmlElementWithText(ename, txt, ...) XmlElementWithTextXS(xs, ename, txt, ##__VA_ARGS__) + +XmlMemSummaryReporter::XmlMemSummaryReporter(MemBaseline& baseline, outputStream* output, size_t scale) : + _malloc_snapshot(baseline.malloc_memory_snapshot()), + _vm_snapshot(baseline.virtual_memory_snapshot()), + _instance_class_count(baseline.instance_class_count()), + _array_class_count(baseline.array_class_count()) , + _scale(scale) { + _xml_output = new (mtNMT) xmlStream(output); +} + +XmlMemSummaryReporter::~XmlMemSummaryReporter() { + if(_xml_output != nullptr) { + _xml_output->flush(); + } + delete _xml_output; +} + +void XmlMemSummaryReporter::print_malloc(const MemoryCounter* c, MemTag mem_tag) const { + xmlStream* xs = xml_output(); + + const size_t amount = amount_in_current_scale(c->size()); + const size_t count = c->count(); + const size_t pk_amount = amount_in_current_scale(c->peak_size()); + XmlParentElement("malloc"); + XmlElementWithText("memoryTag", "%s", NMTUtil::tag_to_name(mem_tag)); + XmlElementWithText(mem_tag == mtThreadStack ? "threadStack" : "malloc", "%zu", amount); + XmlElementWithText("count", "%zu", count); + XmlElementWithText("atPeak", "%d", pk_amount == amount); + XmlElementWithText("amountPeak", "%zu", pk_amount); + XmlElementWithText("countPeak", "%zu", c->peak_count()); +} + +void XmlMemSummaryReporter::print_virtual_memory(size_t reserved, size_t committed, size_t peak) const { + xmlStream* xs = xml_output(); + XmlParentElement("mmap"); + XmlElementWithText("reserved", "%zu", amount_in_current_scale(reserved)); + XmlElementWithText("committed", "%zu", amount_in_current_scale(committed)); + XmlElementWithText("atPeak", "%d", peak == committed); + XmlElementWithText("peak", "%zu", amount_in_current_scale(peak)); +} + +void XmlMemSummaryReporter::print_arena(const MemoryCounter* c) const { + xmlStream* xs = xml_output(); + + const size_t amount = c->size(); + const size_t count = c->count(); + const size_t pk_amount = c->peak_size(); + const size_t pk_count = c->peak_count(); + + XmlParentElement("arena"); + XmlElementWithText("amount", "%zu", amount_in_current_scale(amount)); + XmlElementWithText("count", "%zu", amount_in_current_scale(count)); + XmlElementWithText("atPeak", "%d", pk_amount == amount); + XmlElementWithText("countPeak", "%zu", amount_in_current_scale(pk_count)); +} + +void XmlMemSummaryReporter::print_virtual_memory_region(const char* type, address base, size_t size) const { + xmlStream* xs = xml_output(); + XmlParentElement("region"); + XmlElementWithText("base", PTR_FORMAT, p2i(base)); + XmlElementWithText("end", PTR_FORMAT, p2i(base+size)); + XmlElementWithText("size", "%zu", amount_in_current_scale(size)); + XmlElementWithText("state", "%s", type); +} + +void XmlMemSummaryReporter::report(bool summary_only) const { + + xmlStream* xs = xml_output(); + assert(xs != nullptr, "sanity"); + const size_t total_malloced_bytes = _malloc_snapshot->total(); + const size_t total_mmap_reserved_bytes = _vm_snapshot->total_reserved(); + const size_t total_mmap_committed_bytes = _vm_snapshot->total_committed(); + const size_t total_reserved_amount = total_malloced_bytes + total_mmap_reserved_bytes; + const size_t total_committed_amount = total_malloced_bytes + total_mmap_committed_bytes; + + xs->head("nativeMemoryTracking scale=\"%s\"", current_scale()); + XmlElementWithText("report", summary_only ? "Summary" : "Detail"); + { + XmlParentElement("total"); + XmlElementWithText("reserved", "%zu", amount_in_current_scale(total_reserved_amount)); + XmlElementWithText("committed", "%zu", amount_in_current_scale(total_committed_amount)); + } + { + XmlParentElement("malloc"); + XmlElementWithText("size", "%zu", amount_in_current_scale(total_malloced_bytes)); + XmlElementWithText("count", "%zu", _malloc_snapshot->total_count()); + XmlElementWithText("sizePeak", "%zu", amount_in_current_scale(_malloc_snapshot->total_peak())); + XmlElementWithText("countPeak", "%zu", _malloc_snapshot->total_peak_count()); + } + { + XmlParentElement("mmap"); + XmlElementWithText("reserved", "%zu", amount_in_current_scale(total_mmap_reserved_bytes)); + XmlElementWithText("committed", "%zu", amount_in_current_scale(total_mmap_committed_bytes)); + } + { + XmlParentElement("memoryTags"); + for (int index = 0; index < mt_number_of_tags; index ++) { + MemTag mem_tag = NMTUtil::index_to_tag(index); + if (mem_tag == mtThreadStack) continue; + + MallocMemory* malloc_memory = _malloc_snapshot->by_tag(mem_tag); + VirtualMemory* virtual_memory = _vm_snapshot->by_tag(mem_tag); + + report_summary_of_tag(mem_tag, malloc_memory, virtual_memory); + } + } + if (summary_only) { + xs->tail("nativeMemoryTracking"); + } +} + +void XmlMemSummaryReporter::report_summary_of_tag(MemTag mem_tag, + MallocMemory* malloc_memory, VirtualMemory* virtual_memory) const { + + size_t reserved_amount = MemReporterBase::reserved_total (malloc_memory, virtual_memory); + size_t committed_amount = MemReporterBase::committed_total(malloc_memory, virtual_memory); + + // Count thread's native stack in "Thread" category + if (mem_tag == mtThread) { + const VirtualMemory* thread_stack_usage = + (const VirtualMemory*)_vm_snapshot->by_tag(mtThreadStack); + reserved_amount += thread_stack_usage->reserved(); + committed_amount += thread_stack_usage->committed(); + } else if (mem_tag == mtNMT) { + // Count malloc headers in "NMT" category + reserved_amount += _malloc_snapshot->malloc_overhead(); + committed_amount += _malloc_snapshot->malloc_overhead(); + } + + // Omit printing if the current reserved value as well as all historical peaks (malloc, mmap committed, arena) + // fall below scale threshold + const size_t pk_vm = virtual_memory->peak_size(); + const size_t pk_malloc = malloc_memory->malloc_peak_size(); + const size_t pk_arena = malloc_memory->arena_peak_size(); + + if (amount_in_current_scale(MAX4(reserved_amount, pk_vm, pk_malloc, pk_arena)) == 0) { + return; + } + + xmlStream* xs = xml_output(); + XmlParentElement("memoryTag"); + XmlElementWithText("name", "%s", NMTUtil::tag_to_name(mem_tag)); + { + XmlParentElement("total"); + XmlElementWithText("reserved", "%zu", amount_in_current_scale(reserved_amount)); + XmlElementWithText("committed", "%zu", amount_in_current_scale(committed_amount)); + } +#if INCLUDE_CDS + if (mem_tag == mtClassShared) { + size_t read_only_bytes = FileMapInfo::readonly_total(); + XmlElementWithText("readonly", "%zu", amount_in_current_scale(read_only_bytes)); + } +#endif + + + + if (mem_tag == mtClass) { + // report class count + XmlElementWithText("classes", "%zu", _instance_class_count + _array_class_count); + XmlElementWithText("instanceClasses", "%zu", _instance_class_count); + XmlElementWithText("arrayClasses", "%zu", _array_class_count); + } else if (mem_tag == mtThread) { + const VirtualMemory* thread_stack_usage =_vm_snapshot->by_tag(mtThreadStack); + // report thread count + XmlElementWithText("threads", "%zu", ThreadStackTracker::thread_count()); + { + XmlParentElement("threadStack"); + XmlElementWithText("reserved", "%zu", thread_stack_usage->reserved()); + XmlElementWithText("committed", "%zu", thread_stack_usage->committed()); + XmlElementWithText("peak", "%zu", thread_stack_usage->peak_size()); + } + } + + // report malloc'd memory + if (amount_in_current_scale(MAX2(malloc_memory->malloc_size(), pk_malloc)) > 0) { + print_malloc(malloc_memory->malloc_counter(), mem_tag); + } + + if (amount_in_current_scale(MAX2(virtual_memory->reserved(), pk_vm)) > 0) { + print_virtual_memory(virtual_memory->reserved(), virtual_memory->committed(), virtual_memory->peak_size()); + } + + if (amount_in_current_scale(MAX2(malloc_memory->arena_size(), pk_arena)) > 0) { + print_arena(malloc_memory->arena_counter()); + } + + if (mem_tag == mtNMT && + amount_in_current_scale(_malloc_snapshot->malloc_overhead()) > 0) { + XmlElementWithText("trackingOverhead", "%zu", amount_in_current_scale(_malloc_snapshot->malloc_overhead())); + } else if (mem_tag == mtClass) { + // Metadata information + report_metadata(Metaspace::NonClassType); + if (Metaspace::using_class_space()) { + report_metadata(Metaspace::ClassType); + } + } + + +} + +void XmlMemSummaryReporter::report_metadata(Metaspace::MetadataType type) const { + + // NMT reports may be triggered (as part of error handling) very early. Make sure + // Metaspace is already initialized. + if (!Metaspace::initialized()) { + return; + } + + assert(type == Metaspace::NonClassType || type == Metaspace::ClassType, "Invalid metadata type"); + const char* name = (type == Metaspace::NonClassType) ? "metadata" : "classSpace"; + + xmlStream* xs = xml_output(); + const MetaspaceStats stats = MetaspaceUtils::get_statistics(type); + + size_t waste = stats.committed() - stats.used(); + float waste_percentage = stats.committed() > 0 ? (((float)waste * 100)/(float)stats.committed()) : 0.0f; + + XmlParentElement(name); + { + XmlParentElement("total"); + XmlElementWithText("reserved", "%zu", amount_in_current_scale(stats.reserved())); + XmlElementWithText("committed", "%zu", amount_in_current_scale(stats.committed())); + XmlElementWithText("used", "%zu", amount_in_current_scale(stats.used())); + XmlElementWithText("waste", "%zu", amount_in_current_scale(waste)); + XmlElementWithText("wastePercentage", "%2.2f", waste_percentage); + } +} + +void XmlMemDetailReporter::report() const { + XmlMemSummaryReporter::report(/*summary only*/false); + report_virtual_memory_map(); + report_memory_file_allocations(); + report_detail(); + xml_output()->tail("nativeMemoryTracking"); +} + +void XmlMemDetailReporter::report_detail() const { + xmlStream* xs = xml_output(); + + XmlParentElement("details"); + + OmittedAllocations malloc_omitted = report_malloc_sites(); + OmittedAllocations vma_omitted = report_virtual_memory_allocation_sites(); + { + XmlParentElement("omitted"); + { + XmlParentElement("malloc"); + XmlElementWithText("amount", "%zu", malloc_omitted.amount); + XmlElementWithText("count", "%d", malloc_omitted.count); + } + { + XmlParentElement("virtualMemory"); + XmlElementWithText("amount", "%zu", vma_omitted.amount); + XmlElementWithText("count", "%d", vma_omitted.count); + } + } +} + +XmlMemDetailReporter::OmittedAllocations XmlMemDetailReporter::report_malloc_sites() const { + MallocSiteIterator malloc_itr = _baseline.malloc_sites(MemBaseline::by_size); + OmittedAllocations omitted; + if (malloc_itr.is_empty()) return omitted; + + xmlStream* xs = xml_output(); + + const MallocSite* malloc_site; + + XmlParentElement("mallocSites"); + while ((malloc_site = malloc_itr.next()) != nullptr) { + // Omit printing if the current value and the historic peak value both fall below the reporting scale threshold + if (amount_in_current_scale(MAX2(malloc_site->size(), malloc_site->peak_size())) == 0) { + omitted.count ++; + omitted.amount += malloc_site->size(); + continue; + } + XmlParentElement("mallocSite"); + { + XmlStackElement; + _stackprinter.print_stack(malloc_site->call_stack()); + + } + MemTag mem_tag = malloc_site->mem_tag(); + assert(NMTUtil::tag_is_valid(mem_tag) && mem_tag != mtNone, "Must have a valid memory tag"); + { + XmlParentElement("malloc"); + print_malloc(malloc_site->counter(), mem_tag); + } + } + return omitted; +} + +XmlMemDetailReporter::OmittedAllocations XmlMemDetailReporter::report_virtual_memory_allocation_sites() const { + VirtualMemorySiteIterator virtual_memory_itr = + _baseline.virtual_memory_sites(MemBaseline::by_size); + + OmittedAllocations omitted; + if (virtual_memory_itr.is_empty()) return omitted; + + xmlStream* xs = xml_output(); + + const VirtualMemoryAllocationSite* virtual_memory_site; + XmlParentElement("virtualMemoryAllocationSites"); + while ((virtual_memory_site = virtual_memory_itr.next()) != nullptr) { + // Don't report free sites; does not count toward omitted count. + if (virtual_memory_site->reserved() == 0) { + continue; + } + // Omit printing if the current value and the historic peak value both fall below the + // reporting scale threshold + if (amount_in_current_scale(MAX2(virtual_memory_site->reserved(), + virtual_memory_site->peak_size())) == 0) { + omitted.count++; + omitted.amount += virtual_memory_site->reserved(); + continue; + } + { + XmlParentElement("AllocSite"); + { + XmlStackElement; + _stackprinter.print_stack(virtual_memory_site->call_stack()); + } + { + XmlParentElement("total"); + XmlElementWithText("reserved", "%zu", virtual_memory_site->reserved()); + XmlElementWithText("committed", "%zu", virtual_memory_site->committed()); + XmlElementWithText("memoryTag", "%s", NMTUtil::tag_to_name(virtual_memory_site->mem_tag())); + } + } + } + return omitted; +} + +void XmlMemDetailReporter::report_virtual_memory_map() const{ + // Virtual memory map always in base address order + VirtualMemoryAllocationIterator itr = _baseline.virtual_memory_allocations(); + const ReservedMemoryRegion* rgn; + xmlStream* xs = xml_output(); + XmlParentElement("virtualMemoryMap"); + while ((rgn = itr.next()) != nullptr) { + XmlParentElement("region"); + report_virtual_memory_region(rgn); + } +} + +void XmlMemDetailReporter::report_virtual_memory_region(const ReservedMemoryRegion* reserved_rgn) const { + assert(reserved_rgn != nullptr, "null pointer"); + + // We don't bother about reporting peaks here. + // That is because peaks - in the context of virtual memory, peak of committed areas - make little sense + // when we report *by region*, which are identified by their location in memory. There is a philosophical + // question about identity here: e.g. a committed region that has been split into three regions by + // uncommitting a middle section of it, should that still count as "having peaked" before the split? If + // yes, which of the three new regions would be the spiritual successor? Rather than introducing more + // complexity, we avoid printing peaks altogether. Note that peaks should still be printed when reporting + // usage *by callsite*. + + // Don't report if size is too small. + if (amount_in_current_scale(reserved_rgn->size()) == 0) return; + xmlStream* xs = xml_output(); + const NativeCallStack* stack = reserved_rgn->call_stack(); + bool all_committed = reserved_rgn->size() == VirtualMemoryTracker::Instance::committed_size(reserved_rgn); + const char* region_type = (all_committed ? "reservedAndCommitted" : "reserved"); + + print_virtual_memory_region(region_type, reserved_rgn->base(), reserved_rgn->size()); + + XmlElementWithText("memoryTag", "%s", NMTUtil::tag_to_name(reserved_rgn->mem_tag())); + + { + XmlStackElement; + _stackprinter.print_stack(stack); + } + + if (all_committed) { + bool reserved_and_committed = false; + VirtualMemoryTracker::Instance::tree()->visit_committed_regions(*reserved_rgn, + [&](CommittedMemoryRegion& committed_rgn) { + if (committed_rgn.equals(*reserved_rgn)) { + // One region spanning the entire reserved region, with the same stack trace. + // Don't print this regions because the "reserved and committed" line above + // already indicates that the region is committed. + reserved_and_committed = true; + return false; + } + return true; + }); + + if (reserved_and_committed) { + return; + } + } + auto print_committed_rgn = [&](const CommittedMemoryRegion& crgn) { + XmlParentElement("committedRegion"); + + print_virtual_memory_region("committed", crgn.base(), crgn.size()); + { + XmlStackElement; + _stackprinter.print_stack(crgn.call_stack()); + } + }; + + VirtualMemoryTracker::Instance::tree()->visit_committed_regions(*reserved_rgn, + [&](CommittedMemoryRegion& crgn) { + print_committed_rgn(crgn); + return true; + }); +} + +void XmlMemDetailReporter::report_memory_file_allocations() const { + MemTracker::NmtVirtualMemoryLocker nvml; + MemoryFileTracker::Instance::print_all_reports_xml_on(xml_output(), scale()); +} + +void XmlMemSummaryDiffReporter::report_diff(bool summary_only) const { + xmlStream* xs = xml_output(); + xs->head("nativeMemoryTracking scale=\"%s\"", current_scale()); + XmlElementWithText("report", summary_only ? "Summary Diff" : "Detail Diff"); + + // Overall diff + { + XmlParentElement("total"); + print_virtual_memory_diff(_current_baseline.total_reserved_memory(), + _current_baseline.total_committed_memory(), + _early_baseline.total_reserved_memory(), + _early_baseline.total_committed_memory()); + } + + // malloc diff + const size_t early_malloced_bytes = _early_baseline.malloc_memory_snapshot()->total(); + const size_t early_count = _early_baseline.malloc_memory_snapshot()->total_count(); + const size_t current_malloced_bytes = _current_baseline.malloc_memory_snapshot()->total(); + const size_t current_count = _current_baseline.malloc_memory_snapshot()->total_count(); + { + XmlParentElement("malloc"); + print_malloc_diff(current_malloced_bytes, + current_count, + early_malloced_bytes, + early_count, mtNone); + } + // mmap diff + { + XmlParentElement("virtualMemoryDiff"); + print_virtual_memory_diff(_current_baseline.virtual_memory_snapshot()->total_reserved(), + _current_baseline.virtual_memory_snapshot()->total_committed(), + _early_baseline.virtual_memory_snapshot()->total_reserved(), + _early_baseline.virtual_memory_snapshot()->total_committed()); + } + + // Summary diff by memory tag + for (int index = 0; index < mt_number_of_tags; index ++) { + MemTag mem_tag = NMTUtil::index_to_tag(index); + // thread stack is reported as part of thread category + if (mem_tag == mtThreadStack) continue; + + { + XmlParentElement("memoryTag"); + diff_summary_of_tag(mem_tag, + _early_baseline.malloc_memory(mem_tag), + _early_baseline.virtual_memory(mem_tag), + _early_baseline.metaspace_stats(), + _current_baseline.malloc_memory(mem_tag), + _current_baseline.virtual_memory(mem_tag), + _current_baseline.metaspace_stats()); + } + } + if (summary_only) { + xs->tail("nativeMemoryTracking"); + } +} + +void XmlMemSummaryDiffReporter::print_malloc_diff(size_t current_amount, size_t current_count, + size_t early_amount, size_t early_count, MemTag mem_tag) const { + xmlStream* xs = xml_output(); + + XmlParentElement("mallocDiff"); + XmlElementWithText("amount", "%zu", amount_in_current_scale(current_amount)); + // Report type only if it is valid and not under "thread" category + if (mem_tag != mtNone && mem_tag != mtThread) { + XmlElementWithText("memoryTag", "%s", NMTUtil::tag_to_name(mem_tag)); + } + + XmlElementWithText("amountDiff", INT64_PLUS_FORMAT, diff_in_current_scale(current_amount, early_amount)); + XmlElementWithText("count", "%zu", current_count); + XmlElementWithText("countDiff", " %+zd", counter_diff(current_count, early_count)); +} + +void XmlMemSummaryDiffReporter::print_arena_diff(size_t current_amount, size_t current_count, + size_t early_amount, size_t early_count) const { + xmlStream* xs = xml_output(); + XmlParentElement("arenaDiff"); + XmlElementWithText("amount", "%zu", amount_in_current_scale(current_amount)); + XmlElementWithText("amountDiff", INT64_PLUS_FORMAT, diff_in_current_scale(current_amount, early_amount)); + XmlElementWithText("count", "%zu", current_count); + XmlElementWithText("countDiff", "%+zd", counter_diff(current_count, early_count)); +} + +void XmlMemSummaryDiffReporter::print_virtual_memory_diff(size_t current_reserved, size_t current_committed, + size_t early_reserved, size_t early_committed) const { + + xmlStream* xs = xml_output(); + + XmlParentElement("vmDiff"); + XmlElementWithText("reservedCurrent", "%zu", amount_in_current_scale(current_reserved)); + XmlElementWithText("reservedDiff", INT64_PLUS_FORMAT, diff_in_current_scale(current_reserved, early_reserved)); + XmlElementWithText("committedCurrent", "%zu", amount_in_current_scale(current_committed)); + XmlElementWithText("committedDiff", INT64_PLUS_FORMAT, diff_in_current_scale(current_committed, early_committed)); +} + + +void XmlMemSummaryDiffReporter::diff_summary_of_tag(MemTag mem_tag, + const MallocMemory* early_malloc, const VirtualMemory* early_vm, + const MetaspaceCombinedStats& early_ms, + const MallocMemory* current_malloc, const VirtualMemory* current_vm, + const MetaspaceCombinedStats& current_ms) const { + + xmlStream* xs = xml_output(); + const char* scale = current_scale(); + constexpr int indent = 28; + + // Total reserved and committed memory in current baseline + size_t current_reserved_amount = reserved_total (current_malloc, current_vm); + size_t current_committed_amount = committed_total(current_malloc, current_vm); + + // Total reserved and committed memory in early baseline + size_t early_reserved_amount = reserved_total(early_malloc, early_vm); + size_t early_committed_amount = committed_total(early_malloc, early_vm); + + // Adjust virtual memory total + if (mem_tag == mtThread) { + const VirtualMemory* early_thread_stack_usage = + _early_baseline.virtual_memory(mtThreadStack); + const VirtualMemory* current_thread_stack_usage = + _current_baseline.virtual_memory(mtThreadStack); + + early_reserved_amount += early_thread_stack_usage->reserved(); + early_committed_amount += early_thread_stack_usage->committed(); + + current_reserved_amount += current_thread_stack_usage->reserved(); + current_committed_amount += current_thread_stack_usage->committed(); + } else if (mem_tag == mtNMT) { + early_reserved_amount += _early_baseline.malloc_tracking_overhead(); + early_committed_amount += _early_baseline.malloc_tracking_overhead(); + + current_reserved_amount += _current_baseline.malloc_tracking_overhead(); + current_committed_amount += _current_baseline.malloc_tracking_overhead(); + } + + if (amount_in_current_scale(current_reserved_amount) > 0 || + diff_in_current_scale(current_reserved_amount, early_reserved_amount) != 0) { + + // print summary line + XmlElementWithText("name", "%s", NMTUtil::tag_to_name(mem_tag)); + print_virtual_memory_diff(current_reserved_amount, current_committed_amount, + early_reserved_amount, early_committed_amount); + + // detail lines + if (mem_tag == mtClass) { + // report class count + { + XmlParentElement("classes"); + XmlElementWithText("count", "%zu", _current_baseline.class_count()); + XmlElementWithText("countDiff", "%+zd", counter_diff(_current_baseline.class_count(), _early_baseline.class_count())); + } + { + XmlParentElement("instanceClasses"); + XmlElementWithText("count", "%zu", _current_baseline.instance_class_count()); + XmlElementWithText("countDiff", "%+zd", counter_diff(_current_baseline.instance_class_count(), + _early_baseline.instance_class_count())); + } + { + XmlParentElement("arrayClasses"); + XmlElementWithText("count", "%zu", _current_baseline.array_class_count()); + XmlElementWithText("countDiff", "%+zd", counter_diff(_current_baseline.array_class_count(), + _early_baseline.array_class_count())); + } + + } else if (mem_tag == mtThread) { + { + XmlParentElement("thread"); + XmlElementWithText("count", "%zu", _current_baseline.thread_count()); + XmlElementWithText("countDiff", "%+zd", counter_diff(_current_baseline.thread_count(), + _early_baseline.thread_count())); + } + { + XmlParentElement("stackVirtualMemory"); + const VirtualMemory* current_thread_stack = _current_baseline.virtual_memory(mtThreadStack); + const VirtualMemory* early_thread_stack = _early_baseline.virtual_memory(mtThreadStack); + + print_virtual_memory_diff(current_thread_stack->reserved(), + current_thread_stack->committed(), + early_thread_stack->reserved(), + early_thread_stack->committed()); + + } + } + + // Report malloc'd memory + size_t current_malloc_amount = current_malloc->malloc_size(); + size_t early_malloc_amount = early_malloc->malloc_size(); + if (amount_in_current_scale(current_malloc_amount) > 0 || + diff_in_current_scale(current_malloc_amount, early_malloc_amount) != 0) { + XmlParentElement("mallocDiff"); + print_malloc_diff(current_malloc_amount, (mem_tag == mtChunk) ? 0 : current_malloc->malloc_count(), + early_malloc_amount, early_malloc->malloc_count(), mtNone); + } + + // Report virtual memory + if (amount_in_current_scale(current_vm->reserved()) > 0 || + diff_in_current_scale(current_vm->reserved(), early_vm->reserved()) != 0) { + XmlParentElement("mmapDiff"); + print_virtual_memory_diff(current_vm->reserved(), current_vm->committed(), + early_vm->reserved(), early_vm->committed()); + } + + // Report arena memory + if (amount_in_current_scale(current_malloc->arena_size()) > 0 || + diff_in_current_scale(current_malloc->arena_size(), early_malloc->arena_size()) != 0) { + XmlParentElement("arenaDiff"); + print_arena_diff(current_malloc->arena_size(), current_malloc->arena_count(), + early_malloc->arena_size(), early_malloc->arena_count()); + } + + // Report native memory tracking overhead + if (mem_tag == mtNMT) { + size_t current_tracking_overhead = amount_in_current_scale(_current_baseline.malloc_tracking_overhead()); + size_t early_tracking_overhead = amount_in_current_scale(_early_baseline.malloc_tracking_overhead()); + int64_t overhead_diff = diff_in_current_scale(_current_baseline.malloc_tracking_overhead(), + _early_baseline.malloc_tracking_overhead()); + XmlParentElement("trackingOverhead"); + XmlElementWithText("amount", "%zu", amount_in_current_scale(_current_baseline.malloc_tracking_overhead())); + XmlElementWithText("amountDiff", INT64_PLUS_FORMAT, overhead_diff); + + } else if (mem_tag == mtClass) { + XmlParentElement("metaspaceDiff"); + print_metaspace_diff(current_ms, early_ms); + } + } +} + +void XmlMemSummaryDiffReporter::print_metaspace_diff(const MetaspaceCombinedStats& current_ms, + const MetaspaceCombinedStats& early_ms) const { + print_metaspace_diff("metadata", current_ms.non_class_space_stats(), early_ms.non_class_space_stats()); + if (Metaspace::using_class_space()) { + print_metaspace_diff("classSpace", current_ms.class_space_stats(), early_ms.class_space_stats()); + } +} + +void XmlMemSummaryDiffReporter::print_metaspace_diff(const char* header, + const MetaspaceStats& current_stats, + const MetaspaceStats& early_stats) const { + + xmlStream* xs = xml_output(); + const char* scale = current_scale(); + + XmlParentElement(header); + print_virtual_memory_diff(current_stats.reserved(), + current_stats.committed(), + early_stats.reserved(), + early_stats.committed()); + + int64_t diff_used = diff_in_current_scale(current_stats.used(), + early_stats.used()); + + size_t current_waste = current_stats.committed() - current_stats.used(); + size_t early_waste = early_stats.committed() - early_stats.used(); + int64_t diff_waste = diff_in_current_scale(current_waste, early_waste); + + // Diff used + XmlElementWithText("used", "%zu", amount_in_current_scale(current_stats.used())); + XmlElementWithText("usedDiff", INT64_PLUS_FORMAT, diff_used); + + // Diff waste + const float waste_percentage = current_stats.committed() == 0 ? 0.0f : + ((float)current_waste * 100.0f) / (float)current_stats.committed(); + XmlElementWithText("waste", "%zu", amount_in_current_scale(current_waste)); + XmlElementWithText("wastePercentage", "%2.2f", waste_percentage); + XmlElementWithText("wasteDiff", INT64_PLUS_FORMAT, diff_waste); +} + +size_t XmlMemSummaryDiffReporter::reserved_total(const MallocMemory* malloc, const VirtualMemory* vm) { + return malloc->malloc_size() + malloc->arena_size() + vm->reserved(); +} + +size_t XmlMemSummaryDiffReporter::committed_total(const MallocMemory* malloc, const VirtualMemory* vm) { + return malloc->malloc_size() + malloc->arena_size() + vm->committed(); +} + +void XmlMemDetailDiffReporter::report_diff() const { + XmlMemSummaryDiffReporter::report_diff(/*summary only*/false); + diff_malloc_sites(); + diff_virtual_memory_sites(); + xml_output()->tail("nativeMemoryTracking"); +} + +void XmlMemDetailDiffReporter::diff_malloc_sites() const { + MallocSiteIterator early_itr = _early_baseline.malloc_sites(MemBaseline::by_site_and_tag); + MallocSiteIterator current_itr = _current_baseline.malloc_sites(MemBaseline::by_site_and_tag); + + const MallocSite* early_site = early_itr.next(); + const MallocSite* current_site = current_itr.next(); + + while (early_site != nullptr || current_site != nullptr) { + if (early_site == nullptr) { + new_malloc_site(current_site); + current_site = current_itr.next(); + } else if (current_site == nullptr) { + old_malloc_site(early_site); + early_site = early_itr.next(); + } else { + int compVal = current_site->call_stack()->compare(*early_site->call_stack()); + if (compVal < 0) { + new_malloc_site(current_site); + current_site = current_itr.next(); + } else if (compVal > 0) { + old_malloc_site(early_site); + early_site = early_itr.next(); + } else { + diff_malloc_site(early_site, current_site); + early_site = early_itr.next(); + current_site = current_itr.next(); + } + } + } +} + +void XmlMemDetailDiffReporter::diff_virtual_memory_sites() const { + VirtualMemorySiteIterator early_itr = _early_baseline.virtual_memory_sites(MemBaseline::by_site); + VirtualMemorySiteIterator current_itr = _current_baseline.virtual_memory_sites(MemBaseline::by_site); + + const VirtualMemoryAllocationSite* early_site = early_itr.next(); + const VirtualMemoryAllocationSite* current_site = current_itr.next(); + + while (early_site != nullptr || current_site != nullptr) { + if (early_site == nullptr) { + new_virtual_memory_site(current_site); + current_site = current_itr.next(); + } else if (current_site == nullptr) { + old_virtual_memory_site(early_site); + early_site = early_itr.next(); + } else { + int compVal = current_site->call_stack()->compare(*early_site->call_stack()); + if (compVal < 0) { + new_virtual_memory_site(current_site); + current_site = current_itr.next(); + } else if (compVal > 0) { + old_virtual_memory_site(early_site); + early_site = early_itr.next(); + } else if (early_site->mem_tag() != current_site->mem_tag()) { + // This site was originally allocated with one memory tag, then released, + // then re-allocated at the same site (as far as we can tell) with a different memory tag. + old_virtual_memory_site(early_site); + early_site = early_itr.next(); + new_virtual_memory_site(current_site); + current_site = current_itr.next(); + } else { + diff_virtual_memory_site(early_site, current_site); + early_site = early_itr.next(); + current_site = current_itr.next(); + } + } + } +} + + +void XmlMemDetailDiffReporter::new_malloc_site(const MallocSite* malloc_site) const { + diff_malloc_site(malloc_site->call_stack(), malloc_site->size(), malloc_site->count(), + 0, 0, malloc_site->mem_tag()); +} + +void XmlMemDetailDiffReporter::old_malloc_site(const MallocSite* malloc_site) const { + diff_malloc_site(malloc_site->call_stack(), 0, 0, malloc_site->size(), + malloc_site->count(), malloc_site->mem_tag()); +} + +void XmlMemDetailDiffReporter::diff_malloc_site(const MallocSite* early, + const MallocSite* current) const { + if (early->mem_tag() != current->mem_tag()) { + // If malloc site type changed, treat it as deallocation of old type and + // allocation of new type. + old_malloc_site(early); + new_malloc_site(current); + } else { + diff_malloc_site(current->call_stack(), current->size(), current->count(), + early->size(), early->count(), early->mem_tag()); + } +} + +void XmlMemDetailDiffReporter::diff_malloc_site(const NativeCallStack* stack, size_t current_size, + size_t current_count, size_t early_size, size_t early_count, MemTag mem_tag) const { + outputStream* out = xml_output(); + + assert(stack != nullptr, "null stack"); + + if (diff_in_current_scale(current_size, early_size) == 0) { + return; + } + XmlParentElement("mallocSiteDiff"); + { + XmlStackElement; + _stackprinter.print_stack(stack); + } + { + XmlParentElement("mallocDiff"); + print_malloc_diff(current_size, current_count, early_size, early_count, mem_tag); + } +} + + +void XmlMemDetailDiffReporter::new_virtual_memory_site(const VirtualMemoryAllocationSite* site) const { + diff_virtual_memory_site(site->call_stack(), site->reserved(), site->committed(), 0, 0, site->mem_tag()); +} + +void XmlMemDetailDiffReporter::old_virtual_memory_site(const VirtualMemoryAllocationSite* site) const { + diff_virtual_memory_site(site->call_stack(), 0, 0, site->reserved(), site->committed(), site->mem_tag()); +} + +void XmlMemDetailDiffReporter::diff_virtual_memory_site(const VirtualMemoryAllocationSite* early, + const VirtualMemoryAllocationSite* current) const { + diff_virtual_memory_site(current->call_stack(), current->reserved(), current->committed(), + early->reserved(), early->committed(), current->mem_tag()); +} + +void XmlMemDetailDiffReporter::diff_virtual_memory_site(const NativeCallStack* stack, size_t current_reserved, + size_t current_committed, size_t early_reserved, size_t early_committed, MemTag mem_tag) const { + xmlStream* xs = xml_output(); + + // no change + if (diff_in_current_scale(current_reserved, early_reserved) == 0 && + diff_in_current_scale(current_committed, early_committed) == 0) { + return; + } + XmlParentElement("virtualMemorySiteDiff"); + { + XmlStackElement; + _stackprinter.print_stack(stack); + } + { + XmlParentElement("mmapDiff"); + print_virtual_memory_diff(current_reserved, current_committed, early_reserved, early_committed); + if (mem_tag != mtNone) { + XmlElementWithText("memoryTag", "%s", NMTUtil::tag_to_name(mem_tag)); + } + } +} diff --git a/src/hotspot/share/nmt/memReporter.hpp b/src/hotspot/share/nmt/memReporter.hpp index bab8de138d0ce..cad646d37f41e 100644 --- a/src/hotspot/share/nmt/memReporter.hpp +++ b/src/hotspot/share/nmt/memReporter.hpp @@ -32,6 +32,7 @@ #include "nmt/nmtCommon.hpp" #include "nmt/virtualMemoryTracker.hpp" #include "utilities/nativeCallStack.hpp" +#include "utilities/xmlstream.hpp" /* * Base class that provides helpers @@ -199,7 +200,7 @@ class MemSummaryDiffReporter : public MemReporterBase { } // Generate summary comparison report - virtual void report_diff(); + void report_diff() const; private: // report the comparison of each mem_tag @@ -238,7 +239,7 @@ class MemDetailDiffReporter : public MemSummaryDiffReporter { _stackprinter(output) { } // Generate detail comparison report - virtual void report_diff(); + void report_diff() const; // Malloc allocation site comparison void diff_malloc_sites() const; @@ -266,4 +267,193 @@ class MemDetailDiffReporter : public MemSummaryDiffReporter { size_t current_committed, size_t early_reserved, size_t early_committed, MemTag mem_tag) const; }; +class XmlMemSummaryReporter : public StackObj { + private: + MallocMemorySnapshot* _malloc_snapshot; + VirtualMemorySnapshot* _vm_snapshot; + size_t _instance_class_count; + size_t _array_class_count; + const size_t _scale; // report in this scale + + protected: + xmlStream* _xml_output; + + public: + static const size_t default_scale = K; + // This constructor is for normal reporting from a recent baseline. + XmlMemSummaryReporter(MemBaseline& baseline, outputStream* output, size_t scale = default_scale); + + inline xmlStream* xml_output() const { return _xml_output; } + + size_t scale() const { + return _scale; + } + inline const char* current_scale() const { + return NMTUtil::scale_name(_scale); + } + // Convert memory amount in bytes to current reporting scale + inline size_t amount_in_current_scale(size_t amount) const { + return NMTUtil::amount_in_scale(amount, _scale); + } + + // Convert diff amount in bytes to current reporting scale + // We use int64_t instead of ssize_t because on 32-bit it allows us to express deltas larger than 2 gb. + // On 64-bit we never expect memory sizes larger than INT64_MAX. + int64_t diff_in_current_scale(size_t s1, size_t s2) const { + assert(_scale != 0, "wrong scale"); + +#ifdef _LP64 + assert(s1 < INT64_MAX, "exceeded possible memory limits"); + assert(s2 < INT64_MAX, "exceeded possible memory limits"); +#endif + + bool is_negative = false; + if (s1 < s2) { + is_negative = true; + swap(s1, s2); + } + + size_t amount = s1 - s2; + // We can split amount into p + q, where + // q = amount % _scale + // and p = amount - q (which is also (amount / _scale) * _scale). + // Then use + // size_t scaled = (p + q + _scale/2) / _scale; + // => + // size_t scaled = (p / _scale) + ((q + _scale/2) / _scale); + // The lefthand side of the addition is exact. + // The righthand side is 0 if q <= (_scale - 1)/2, else 1. (The -1 is to account for odd _scale values.) + size_t scaled = (amount / _scale); + if ((amount % _scale) > (_scale - 1)/2) { + scaled += 1; + } + + int64_t result = static_cast(scaled); + return is_negative ? -result : result; + } + + void report(bool summary_only = true) const; + ~XmlMemSummaryReporter(); + + protected: + void report_summary_of_tag(MemTag mem_tag, MallocMemory* malloc_memory, VirtualMemory* virtual_memory) const; + void report_metadata(Metaspace::MetadataType type) const; + void print_malloc(const MemoryCounter* c, MemTag mem_tag) const; + void print_virtual_memory(size_t reserved, size_t committed, size_t peak) const; + void print_arena(const MemoryCounter* c) const; + void print_virtual_memory_region(const char* type, address base, size_t size) const; +}; + +class XmlMemDetailReporter : public XmlMemSummaryReporter { + private: + MemBaseline& _baseline; + NativeCallStackPrinter _stackprinter; + public: + XmlMemDetailReporter(MemBaseline& baseline, outputStream* output, size_t scale = default_scale) : + XmlMemSummaryReporter(baseline, output, scale), + _baseline(baseline), _stackprinter((outputStream*)_xml_output) { } + + // Generate detail report. + // The report contains summary and detail sections. + void report() const; + private: + struct OmittedAllocations { + size_t amount; + int count; + OmittedAllocations() : amount(0), count(0) { } + }; + // Report detail tracking data. + void report_detail() const; + // Report virtual memory map + void report_virtual_memory_map() const; + // Report all physical devices + void report_memory_file_allocations() const; + // Report malloc allocation sites; returns number of omitted sites + OmittedAllocations report_malloc_sites() const; + // Report virtual memory reservation sites; returns number of omitted sites + OmittedAllocations report_virtual_memory_allocation_sites() const; + + // Report a virtual memory region + void report_virtual_memory_region(const ReservedMemoryRegion* rgn) const; +}; + +class XmlMemSummaryDiffReporter : public XmlMemSummaryReporter { + protected: + MemBaseline& _early_baseline; + MemBaseline& _current_baseline; + + static size_t reserved_total(const MallocMemory* malloc, const VirtualMemory* vm); + static size_t committed_total(const MallocMemory* malloc, const VirtualMemory* vm); + public: + static const size_t default_scale = K; + XmlMemSummaryDiffReporter(MemBaseline& early_baseline, MemBaseline& current_baseline, + outputStream* output, size_t scale = default_scale) : XmlMemSummaryReporter(early_baseline, output) , + _early_baseline(early_baseline), _current_baseline(current_baseline) { + assert(early_baseline.baseline_type() != MemBaseline::Not_baselined, "Not baselined"); + assert(current_baseline.baseline_type() != MemBaseline::Not_baselined, "Not baselined"); + } + + + void report_diff(bool summary_only = true) const; + + private: + // report the comparison of each mem_tag + void diff_summary_of_tag(MemTag mem_tag, + const MallocMemory* early_malloc, const VirtualMemory* early_vm, + const MetaspaceCombinedStats& early_ms, + const MallocMemory* current_malloc, const VirtualMemory* current_vm, + const MetaspaceCombinedStats& current_ms) const; + + protected: + void print_malloc_diff(size_t current_amount, size_t current_count, + size_t early_amount, size_t early_count, MemTag mem_tag) const; + void print_virtual_memory_diff(size_t current_reserved, size_t current_committed, + size_t early_reserved, size_t early_committed) const; + void print_arena_diff(size_t current_amount, size_t current_count, + size_t early_amount, size_t early_count) const; + + void print_metaspace_diff(const MetaspaceCombinedStats& current_ms, + const MetaspaceCombinedStats& early_ms) const; + void print_metaspace_diff(const char* header, + const MetaspaceStats& current_ms, + const MetaspaceStats& early_ms) const; +}; + +class XmlMemDetailDiffReporter : public XmlMemSummaryDiffReporter { + NativeCallStackPrinter _stackprinter; + public: + XmlMemDetailDiffReporter(MemBaseline& early_baseline, MemBaseline& current_baseline, + outputStream* output, size_t scale = default_scale) : + XmlMemSummaryDiffReporter(early_baseline, current_baseline, output, scale), + _stackprinter((outputStream*)xml_output()) { } + + // Generate detail comparison report + void report_diff() const; + + // Malloc allocation site comparison + void diff_malloc_sites() const; + // Virtual memory reservation site comparison + void diff_virtual_memory_sites() const; + + // New malloc allocation site in recent baseline + void new_malloc_site (const MallocSite* site) const; + // The malloc allocation site is not in recent baseline + void old_malloc_site (const MallocSite* site) const; + // Compare malloc allocation site, it is in both baselines + void diff_malloc_site(const MallocSite* early, const MallocSite* current) const; + + // New virtual memory allocation site in recent baseline + void new_virtual_memory_site (const VirtualMemoryAllocationSite* callsite) const; + // The virtual memory allocation site is not in recent baseline + void old_virtual_memory_site (const VirtualMemoryAllocationSite* callsite) const; + // Compare virtual memory allocation site, it is in both baseline + void diff_virtual_memory_site(const VirtualMemoryAllocationSite* early, + const VirtualMemoryAllocationSite* current) const; + + void diff_malloc_site(const NativeCallStack* stack, size_t current_size, + size_t currrent_count, size_t early_size, size_t early_count, MemTag mem_tag) const; + void diff_virtual_memory_site(const NativeCallStack* stack, size_t current_reserved, + size_t current_committed, size_t early_reserved, size_t early_committed, MemTag mem_tag) const; +}; + #endif // SHARE_NMT_MEMREPORTER_HPP diff --git a/src/hotspot/share/nmt/memTracker.cpp b/src/hotspot/share/nmt/memTracker.cpp index a07c8bd69ef70..c7706435cbaff 100644 --- a/src/hotspot/share/nmt/memTracker.cpp +++ b/src/hotspot/share/nmt/memTracker.cpp @@ -165,3 +165,31 @@ void MemTracker::tuning_statistics(outputStream* out) { MallocLimitHandler::print_on(out); out->cr(); } + +void MemTracker::tuning_statistics_xml(outputStream* out) { + xmlStream _xs(out); + xmlStream* xs = &_xs; + assert (!xs->inside_attrs(), "output stream is not ready for XML nodes"); + XmlParent("nativeMemoryTracking"); + XmlElem("report", "statistics"); + { + XmlParent("state"); + XmlElem("level", "%s", NMTUtil::tracking_level_to_string(_tracking_level)); + if (_tracking_level == NMT_detail) { + XmlElem("tableSize", "%d", MallocSiteTable::hash_buckets()); + XmlElem("stackDepth", "%d", NMT_TrackingStackDepth); + { + XmlParent("siteTable"); + MallocSiteTable::print_tuning_statistics_xml(xs); + } + } + { + XmlParent("preinitState"); + NMTPreInit::print_state_xml(xs); + } + { + XmlParent("mallocLimit"); + MallocLimitHandler::print_on_xml(xs); + } + } +} diff --git a/src/hotspot/share/nmt/memTracker.hpp b/src/hotspot/share/nmt/memTracker.hpp index d9ebf4dc30ef7..167722b2b3db6 100644 --- a/src/hotspot/share/nmt/memTracker.hpp +++ b/src/hotspot/share/nmt/memTracker.hpp @@ -36,6 +36,7 @@ #include "utilities/debug.hpp" #include "utilities/deferredStatic.hpp" #include "utilities/nativeCallStack.hpp" +#include "utilities/xmlstream.hpp" #define CURRENT_PC ((MemTracker::tracking_level() == NMT_detail) ? \ NativeCallStack(0) : FAKE_CALLSTACK) @@ -277,6 +278,7 @@ class MemTracker : AllStatic { } static void tuning_statistics(outputStream* out); + static void tuning_statistics_xml(outputStream* out); // MallocLimt: Given an allocation size s, check if mallocing this much // for MemTag would hit either the global limit or the limit for MemTag. diff --git a/src/hotspot/share/nmt/memoryFileTracker.cpp b/src/hotspot/share/nmt/memoryFileTracker.cpp index 2f3f4f1973adb..c08f7043330d9 100644 --- a/src/hotspot/share/nmt/memoryFileTracker.cpp +++ b/src/hotspot/share/nmt/memoryFileTracker.cpp @@ -108,6 +108,78 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st } #endif } +class XmlNodeHelper { + private: + xmlStream* xs; + const char* _name; + public: + XmlNodeHelper(xmlStream* st, const char* name) : xs(st), _name(name) { + xs->head("%s", _name); + } + ~XmlNodeHelper() { + xs->tail(_name); + } +}; +#define XmlNode(txt) XmlNodeHelper _not_used(stream, txt) + +#define ElemText(ename, txt, ...) \ + stream->head(ename); \ + stream->text()->print_cr(txt, ##__VA_ARGS__); \ + stream->tail(ename); + + +void MemoryFileTracker::print_report_xml_on(const MemoryFile* file, xmlStream* stream, size_t scale) { + assert(MemTracker::tracking_level() == NMT_detail, "must"); + + XmlNode("fileMemoryMap"); + ElemText("file", "%s", file->_descriptive_name); + const VMATree::TNode* prev = nullptr; +#ifdef ASSERT + const VMATree::TNode* broken_start = nullptr; + const VMATree::TNode* broken_end = nullptr; +#endif + file->_tree.visit_in_order([&](const VMATree::TNode* current) { + if (prev == nullptr) { + // Must be first node. + prev = current; + return true; + } +#ifdef ASSERT + if (broken_start != nullptr && prev->val().out.mem_tag() != current->val().in.mem_tag()) { + broken_start = prev; + broken_end = current; + } +#endif + if (prev->val().out.type() == VMATree::StateType::Committed) { + const VMATree::position& start_addr = prev->key(); + const VMATree::position& end_addr = current->key(); + XmlNode("region"); + ElemText("base", PTR_FORMAT, start_addr); + ElemText("end", PTR_FORMAT, end_addr); + ElemText("allocated", "%zu", NMTUtil::amount_in_scale(end_addr - start_addr, scale)); + ElemText("scale", "%s", NMTUtil::scale_name(scale)); + ElemText("tag", "%s", NMTUtil::tag_to_name(prev->val().out.mem_tag())); + + { + XmlNode("stack"); + stream->text()->print("\""); + _stack_storage.get(prev->val().out.reserved_stack()).print_on(stream); + stream->text()->print("\""); + } + } + prev = current; + return true; + }); +#ifdef ASSERT + if (broken_start != nullptr) { + tty->print_cr("Broken tree found with first occurrence at nodes %zu, %zu", + broken_start->key(), broken_end->key()); + tty->print_cr("Expected start out to have same type as end in, but was: %s, %s", + VMATree::statetype_to_string(broken_start->val().out.type()), + VMATree::statetype_to_string(broken_end->val().in.type())); + } +#endif +} MemoryFileTracker::MemoryFile* MemoryFileTracker::make_file(const char* descriptive_name) { MemoryFile* file_place = new MemoryFile{descriptive_name}; @@ -158,6 +230,13 @@ void MemoryFileTracker::Instance::print_report_on(const MemoryFile* file, _tracker->print_report_on(file, stream, scale); } +void MemoryFileTracker::Instance::print_report_xml_on(const MemoryFile* file, + xmlStream* stream, size_t scale) { + assert(file != nullptr, "must be"); + assert(stream != nullptr, "must be"); + _tracker->print_report_xml_on(file, stream, scale); +} + void MemoryFileTracker::Instance::print_all_reports_on(outputStream* stream, size_t scale) { const GrowableArrayCHeap& files = MemoryFileTracker::Instance::files(); @@ -170,6 +249,16 @@ void MemoryFileTracker::Instance::print_all_reports_on(outputStream* stream, siz } } +void MemoryFileTracker::Instance::print_all_reports_xml_on(xmlStream* stream, size_t scale) { + const GrowableArrayCHeap& files = + MemoryFileTracker::Instance::files(); + XmlNode("memoryFileDetails"); + for (int i = 0; i < files.length(); i++) { + MemoryFileTracker::MemoryFile* file = files.at(i); + MemoryFileTracker::Instance::print_report_xml_on(file, stream, scale); + } +} + const GrowableArrayCHeap& MemoryFileTracker::Instance::files() { return _tracker->files(); }; diff --git a/src/hotspot/share/nmt/memoryFileTracker.hpp b/src/hotspot/share/nmt/memoryFileTracker.hpp index 1e8bd40ed50f8..68b39955ae52b 100644 --- a/src/hotspot/share/nmt/memoryFileTracker.hpp +++ b/src/hotspot/share/nmt/memoryFileTracker.hpp @@ -35,6 +35,7 @@ #include "utilities/growableArray.hpp" #include "utilities/nativeCallStack.hpp" #include "utilities/ostream.hpp" +#include "utilities/xmlstream.hpp" // The MemoryFileTracker tracks memory of 'memory files', // storage with its own memory space separate from the process. @@ -88,6 +89,7 @@ class MemoryFileTracker { // Print detailed report of file void print_report_on(const MemoryFile* file, outputStream* stream, size_t scale); + void print_report_xml_on(const MemoryFile* file, xmlStream* stream, size_t scale); const GrowableArrayCHeap& files(); @@ -113,7 +115,9 @@ class MemoryFileTracker { static void summary_snapshot(VirtualMemorySnapshot* snapshot); static void print_report_on(const MemoryFile* device, outputStream* stream, size_t scale); + static void print_report_xml_on(const MemoryFile* device, xmlStream* stream, size_t scale); static void print_all_reports_on(outputStream* stream, size_t scale); + static void print_all_reports_xml_on(xmlStream* stream, size_t scale); static const GrowableArrayCHeap& files(); }; diff --git a/src/hotspot/share/nmt/nmtDCmd.cpp b/src/hotspot/share/nmt/nmtDCmd.cpp index 0c6dc5241a2a3..3cefa2e78e77b 100644 --- a/src/hotspot/share/nmt/nmtDCmd.cpp +++ b/src/hotspot/share/nmt/nmtDCmd.cpp @@ -52,7 +52,11 @@ NMTDCmd::NMTDCmd(outputStream* output, _statistics("statistics", "print tracker statistics for tuning purpose.", \ "BOOLEAN", false, "false"), _scale("scale", "Memory usage in which scale, KB, MB or GB", - "STRING", false, "KB") { + "STRING", false, "KB"), + _output_format("format", "print report in text/xml format.", \ + "STRING", false, "text"), + _xml_file_name("file", "The path and file name of the xml output.", + "STRING", false, "nmt.xml") { _dcmdparser.add_dcmd_option(&_summary); _dcmdparser.add_dcmd_option(&_detail); _dcmdparser.add_dcmd_option(&_baseline); @@ -60,6 +64,8 @@ NMTDCmd::NMTDCmd(outputStream* output, _dcmdparser.add_dcmd_option(&_detail_diff); _dcmdparser.add_dcmd_option(&_statistics); _dcmdparser.add_dcmd_option(&_scale); + _dcmdparser.add_dcmd_option(&_output_format); + _dcmdparser.add_dcmd_option(&_xml_file_name); } @@ -137,7 +143,11 @@ void NMTDCmd::execute(DCmdSource source, TRAPS) { } } else if (_statistics.value()) { if (MemTracker::enabled()) { - MemTracker::tuning_statistics(output()); + if (_output_format.has_value() && strcmp(_output_format.value(), "xml") == 0) { + MemTracker::tuning_statistics_xml(output()); + } else { + MemTracker::tuning_statistics(output()); + } } else { output()->print_cr("Native memory tracking is not enabled"); } @@ -150,7 +160,16 @@ void NMTDCmd::execute(DCmdSource source, TRAPS) { void NMTDCmd::report(bool summaryOnly, size_t scale_unit) { MemBaseline baseline; baseline.baseline(summaryOnly); - if (summaryOnly) { + if (_output_format.has_value() && strcmp(_output_format.value(), "xml") == 0) { + if (summaryOnly) { + XmlMemSummaryReporter rpt(baseline, output(), scale_unit); + rpt.report(); + } else { + XmlMemDetailReporter rpt(baseline, output(), scale_unit); + rpt.report(); + } + return; + } else if (summaryOnly) { MemSummaryReporter rpt(baseline, output(), scale_unit); rpt.report(); } else { @@ -168,7 +187,16 @@ void NMTDCmd::report_diff(bool summaryOnly, size_t scale_unit) { MemBaseline baseline; baseline.baseline(summaryOnly); - if (summaryOnly) { + if (_output_format.has_value() && strcmp(_output_format.value(), "xml") == 0) { + if (summaryOnly) { + XmlMemSummaryDiffReporter rpt(early_baseline, baseline, output(), scale_unit); + rpt.report_diff(); + } else { + XmlMemDetailDiffReporter rpt(early_baseline, baseline, output(), scale_unit); + rpt.report_diff(); + } + return; + } else if (summaryOnly) { MemSummaryDiffReporter rpt(early_baseline, baseline, output(), scale_unit); rpt.report_diff(); } else { diff --git a/src/hotspot/share/nmt/nmtDCmd.hpp b/src/hotspot/share/nmt/nmtDCmd.hpp index 2a9f9be49bba1..d163803fb51d3 100644 --- a/src/hotspot/share/nmt/nmtDCmd.hpp +++ b/src/hotspot/share/nmt/nmtDCmd.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -42,9 +42,11 @@ class NMTDCmd: public DCmdWithParser { DCmdArgument _detail_diff; DCmdArgument _statistics; DCmdArgument _scale; + DCmdArgument _output_format; + DCmdArgument _xml_file_name; public: - static int num_arguments() { return 7; } + static int num_arguments() { return 9; } NMTDCmd(outputStream* output, bool heap); static const char* name() { return "VM.native_memory"; } static const char* description() { diff --git a/src/hotspot/share/nmt/nmtPreInit.cpp b/src/hotspot/share/nmt/nmtPreInit.cpp index 3b9150366dc73..e24233d45ca31 100644 --- a/src/hotspot/share/nmt/nmtPreInit.cpp +++ b/src/hotspot/share/nmt/nmtPreInit.cpp @@ -138,6 +138,32 @@ void NMTPreInitAllocationTable::print_state(outputStream* st) const { sum_bytes, longest_chain); } +// print a string describing the current state +void NMTPreInitAllocationTable::print_state_xml(xmlStream* xs) const { + // Collect some statistics and print them + int num_entries = 0; + int num_primary_entries = 0; + int longest_chain = 0; + size_t sum_bytes = 0; + for (int i = 0; i < table_size; i++) { + int chain_len = 0; + for (NMTPreInitAllocation* a = _entries[i]; a != nullptr; a = a->next) { + chain_len++; + sum_bytes += a->size; + } + if (chain_len > 0) { + num_primary_entries++; + } + num_entries += chain_len; + longest_chain = MAX2(chain_len, longest_chain); + } + XmlElem("totalEntries", "%d", num_entries); + XmlElem("primaryEntries", "%d", num_primary_entries); + XmlElem("emptyEntries", "%d", table_size - num_primary_entries); + XmlElem("sumBytes", "%zu", sum_bytes); + XmlElem("longestChainLength", "%d", longest_chain); +} + #ifdef ASSERT void NMTPreInitAllocationTable::print_map(outputStream* st) const { for (int i = 0; i < table_size; i++) { @@ -241,3 +267,12 @@ void NMTPreInit::print_state(outputStream* st) { st->print_cr("pre-init mallocs: %u, pre-init reallocs: %u, pre-init frees: %u", _num_mallocs_pre, _num_reallocs_pre, _num_frees_pre); } + +void NMTPreInit::print_state_xml(xmlStream* xs) { + if (_table != nullptr) { + _table->print_state_xml(xs); + } + XmlElem("preInitMallocCount", "%u", _num_mallocs_pre); + XmlElem("preInitReallocCount", "%u", _num_reallocs_pre); + XmlElem("preInitFreeCount", "%u", _num_frees_pre); +} diff --git a/src/hotspot/share/nmt/nmtPreInit.hpp b/src/hotspot/share/nmt/nmtPreInit.hpp index d85dde816a342..c799573f19336 100644 --- a/src/hotspot/share/nmt/nmtPreInit.hpp +++ b/src/hotspot/share/nmt/nmtPreInit.hpp @@ -31,6 +31,7 @@ #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" +#include "utilities/xmlstream.hpp" #ifdef ASSERT #include "runtime/atomicAccess.hpp" #endif @@ -213,6 +214,7 @@ class NMTPreInitAllocationTable { } void print_state(outputStream* st) const; + void print_state_xml(xmlStream* st) const; DEBUG_ONLY(void print_map(outputStream* st) const;) DEBUG_ONLY(void verify() const;) @@ -373,6 +375,7 @@ class NMTPreInit : public AllStatic { } static void print_state(outputStream* st); + static void print_state_xml(xmlStream* st); static void print_map(outputStream* st); DEBUG_ONLY(static void verify();) }; diff --git a/src/hotspot/share/utilities/xmlstream.hpp b/src/hotspot/share/utilities/xmlstream.hpp index a933d2c472dfe..b7b94876ad182 100644 --- a/src/hotspot/share/utilities/xmlstream.hpp +++ b/src/hotspot/share/utilities/xmlstream.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, 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 @@ -179,6 +179,44 @@ class xmlStream : public outputStream { }; +class XmlElemHelper : public CHeapObjBase { + private: + const char* _node; + + protected: + xmlStream* xs; + + public: + XmlElemHelper(xmlStream* st, const char* node): _node(node), xs(st) { + xs->head("%s", _node); + } + ~XmlElemHelper() { + xs->tail(_node); + } +}; + +class XmlElemStack : public XmlElemHelper { + public: + XmlElemStack(xmlStream* st, const char* text) : XmlElemHelper(st, text) { + st->print_raw("print_raw("]]>"); + } +}; +// Put all the text in one line +#define XmlElementWithTextXS(xs, ename, txt, ...) \ + xs->write("<", 1); \ + xs->text()->print("%s", ename); \ + xs->write(">", 1); \ + xs->text()->print(txt, ##__VA_ARGS__); \ + xs->write("text()->print("%s", ename); \ + xs->write(">\n", 2); + +#define XmlParent(txt) XmlElemHelper __not_used(xs, txt); +#define XmlElem(txt, ...) XmlElementWithTextXS(xs, txt, ##__VA_ARGS__) + // Standard log file, null if no logging is happening. extern xmlStream* xtty; diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdDetailDiffXml.java b/test/hotspot/jtreg/runtime/NMT/JcmdDetailDiffXml.java new file mode 100644 index 0000000000000..24cfd8ff89152 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/JcmdDetailDiffXml.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* + * @test + * @summary run NMT baseline, allocate memory and verify output from detail.diff + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail JcmdDetailDiffXml + */ + +import java.io.File; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.JDKToolFinder; + +import jdk.test.whitebox.WhiteBox; + +public class JcmdDetailDiffXml { + + public static WhiteBox wb = WhiteBox.getWhiteBox(); + + public static NMTXmlUtils runAndCreateXmlReport(String xmlFilename) throws Exception { + File xmlFile = File.createTempFile(xmlFilename, ".xml"); + ProcessBuilder pb = new ProcessBuilder(); + String pid = Long.toString(ProcessTools.getProcessId()); + + pb.redirectOutput(xmlFile) + .command(new String[] { JDKToolFinder.getJDKTool("jcmd"), + pid, + "VM.native_memory", + "detail.diff", + "scale=KB", + "format=xml"}) + .start() + .waitFor(); + return new NMTXmlUtils(xmlFile); + } + + public static void main(String args[]) throws Exception { + OutputAnalyzer output; + NMTXmlUtils nmtXml; + + long commitSize = 128 * 1024; + long reserveSize = 256 * 1024; + long addr; + + // Run 'jcmd VM.native_memory baseline=true' + output = NMTTestUtils.startJcmdVMNativeMemory("baseline=true"); + output.shouldContain("Baseline taken"); + + addr = wb.NMTReserveMemory(reserveSize); + nmtXml = runAndCreateXmlReport("nmt_detail_diff_1_"); + nmtXml.shouldBeReportType("Detail Diff") + .shouldBeReservedCurrentOfTest("256") + .shouldBeReservedDiffOfTest("+256") + .shouldBeCommittedCurrentOfTest("0"); + + wb.NMTCommitMemory(addr, commitSize); + nmtXml = runAndCreateXmlReport("nmt_detail_diff_2_"); + nmtXml.shouldBeReportType("Detail Diff") + .shouldBeReservedCurrentOfTest("256") + .shouldBeReservedDiffOfTest("+256") + .shouldBeCommittedCurrentOfTest("128") + .shouldBeCommittedDiffOfTest("+128"); + + wb.NMTUncommitMemory(addr, commitSize); + nmtXml = runAndCreateXmlReport("nmt_detail_diff_3_"); + nmtXml.shouldBeReportType("Detail Diff") + .shouldBeReservedCurrentOfTest("256") + .shouldBeReservedDiffOfTest("+256") + .shouldBeCommittedCurrentOfTest("0"); + + wb.NMTReleaseMemory(addr, reserveSize); + nmtXml = runAndCreateXmlReport("nmt_detail_diff_4_"); + nmtXml.shouldBeReportType("Detail Diff") + .shouldNotExistTestTag(); + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdScaleDetailXml.java b/test/hotspot/jtreg/runtime/NMT/JcmdScaleDetailXml.java new file mode 100644 index 0000000000000..3cafbe8834194 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/JcmdScaleDetailXml.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* + * @test + * @summary Test the NMT scale parameter with detail tracking level + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -XX:NativeMemoryTracking=detail JcmdScaleDetailXml + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; + +import jdk.test.lib.JDKToolFinder; + +public class JcmdScaleDetailXml { + + public static String[] getCommmand(String pid, String scale, File xmlFile) throws Exception { + return new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "detail", "scale=" + scale, + "format=xml"}; + } + + public static NMTXmlUtils runAndCreateXmlReport(String scale, String xmlFilename) throws Exception { + ProcessBuilder pb = new ProcessBuilder(); + String pid = Long.toString(ProcessTools.getProcessId()); + File xmlFile = File.createTempFile(xmlFilename, ".xml"); + pb.redirectOutput(xmlFile) + .command(getCommmand(pid, scale, xmlFile)) + .start().waitFor(); + return new NMTXmlUtils(xmlFile); + } + + public static void main(String args[]) throws Exception { + NMTXmlUtils nmtXml = runAndCreateXmlReport("KB", "nmt_detail_KB_"); + nmtXml.shouldBeReportType("Detail") + .shouldBeScale("KB"); + + + nmtXml = runAndCreateXmlReport("MB", "nmt_detail_MB_"); + nmtXml.shouldBeReportType("Detail") + .shouldBeScale("MB"); + + nmtXml = runAndCreateXmlReport("GB", "nmt_detail_GB_"); + nmtXml.shouldBeReportType("Detail") + .shouldBeScale("GB"); + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdStatisticsXml.java b/test/hotspot/jtreg/runtime/NMT/JcmdStatisticsXml.java new file mode 100644 index 0000000000000..43e001a47dc08 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/JcmdStatisticsXml.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* + * @test id=summary + * @summary Test the NMT scale parameter + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -XX:NativeMemoryTracking=summary JcmdStatisticsXml summary + */ + + /* + * @test id=detail + * @summary Test the NMT scale parameter + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -XX:NativeMemoryTracking=detail JcmdStatisticsXml detail + */ + +import jdk.test.lib.process.ProcessTools; + +import java.io.File; + +import jdk.test.lib.JDKToolFinder; + +public class JcmdStatisticsXml { + + public static void main(String[] args) throws Exception { + ProcessBuilder pb = new ProcessBuilder(); + File xmlFile = File.createTempFile("nmt_statistics_", ".xml"); + String pid = Long.toString(ProcessTools.getProcessId()); + pb.redirectOutput(xmlFile) + .command(new String[] { JDKToolFinder.getJDKTool("jcmd"), + pid, + "VM.native_memory", + "statistics=true", + "format=xml"}) + .start() + .waitFor(); + + NMTXmlUtils xmlAnalyzer = new NMTXmlUtils(xmlFile); + xmlAnalyzer.shouldBeReportType("statistics") + .shouldExistGeneralStatistics(); + if (args.length == 1 && args[0].equals("detail")) { + xmlAnalyzer.shouldExistDetailStatistics(); + } + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdSummaryDiffXml.java b/test/hotspot/jtreg/runtime/NMT/JcmdSummaryDiffXml.java new file mode 100644 index 0000000000000..554d325c2492c --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/JcmdSummaryDiffXml.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* + * @test + * @summary run NMT baseline, allocate memory and verify output from summary.diff + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=summary JcmdSummaryDiffXml + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; + +import jdk.test.lib.JDKToolFinder; + +import jdk.test.whitebox.WhiteBox; + +public class JcmdSummaryDiffXml { + + public static WhiteBox wb = WhiteBox.getWhiteBox(); + + public static String[] getCommmand(String pid, File xmlFile) throws Exception { + return new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "summary.diff", "scale=KB", + "format=xml"}; + } + + public static NMTXmlUtils runAndCreateXmlReport(String xmlFilename) throws Exception { + ProcessBuilder pb = new ProcessBuilder(); + String pid = Long.toString(ProcessTools.getProcessId()); + File xmlFile = File.createTempFile(xmlFilename, ".xml"); + pb.redirectOutput(xmlFile); + pb.command(getCommmand(pid, xmlFile)); + pb.start().waitFor(); + return new NMTXmlUtils(xmlFile); + } + + public static void main(String args[]) throws Exception { + ProcessBuilder pb = new ProcessBuilder(); + OutputAnalyzer output; + NMTXmlUtils nmtXml; + // Grab my own PID + String pid = Long.toString(ProcessTools.getProcessId()); + + long commitSize = 128 * 1024; + long reserveSize = 256 * 1024; + long addr; + + // Run 'jcmd VM.native_memory baseline=true' + pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "baseline=true"}); + + output = new OutputAnalyzer(pb.start()); + output.shouldContain("Baseline taken"); + + addr = wb.NMTReserveMemory(reserveSize); + nmtXml = runAndCreateXmlReport("nmt_summary_diff_1_"); + nmtXml.shouldBeReportType("Summary Diff") + .shouldBeReservedCurrentOfTest("256") + .shouldBeReservedDiffOfTest("+256") + .shouldBeCommittedCurrentOfTest("0"); + + wb.NMTCommitMemory(addr, commitSize); + nmtXml = runAndCreateXmlReport("nmt_summary_diff_2_"); + nmtXml.shouldBeReportType("Summary Diff") + .shouldBeCommittedCurrentOfTest("128") + .shouldBeCommittedDiffOfTest("+128"); + + wb.NMTUncommitMemory(addr, commitSize); + nmtXml = runAndCreateXmlReport("nmt_summary_diff_3_"); + nmtXml.shouldBeReportType("Summary Diff") + .shouldBeCommittedCurrentOfTest("0"); + + wb.NMTReleaseMemory(addr, reserveSize); + nmtXml = runAndCreateXmlReport("nmt_summary_diff_4_"); + nmtXml.shouldBeReportType("Summary Diff") + .shouldNotExistTestTag(); + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdSummaryXml.java b/test/hotspot/jtreg/runtime/NMT/JcmdSummaryXml.java new file mode 100644 index 0000000000000..f981736216175 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/JcmdSummaryXml.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025, 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. + */ + + /* + * @test + * @summary Check class counters in summary report + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -Xbootclasspath/a:. -XX:NativeMemoryTracking=summary JcmdSummaryXml + */ + +import java.io.File; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.JDKToolFinder; + +public class JcmdSummaryXml { + + public static void main(String args[]) throws Exception { + ProcessBuilder pb = new ProcessBuilder(); + String pid = Long.toString(ProcessTools.getProcessId()); + File xmlFile = File.createTempFile("nmt_summary_", ".xml"); + + pb.redirectOutput(xmlFile); + // Run 'jcmd VM.native_memory baseline=true' + pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), + pid, + "VM.native_memory", + "summary", + "format=xml"}); + pb.start().waitFor(); + + NMTXmlUtils xmlAnalyzer = new NMTXmlUtils(xmlFile); + xmlAnalyzer.shouldBeReportType("Summary") + .shouldExistClasses() + .shouldExistInstanceClasses() + .shouldExistArrayClasses(); + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/JcmdWithNMTDisabled.java b/test/hotspot/jtreg/runtime/NMT/JcmdWithNMTDisabled.java index 4e044311bcbf6..a45b14c5080fd 100644 --- a/test/hotspot/jtreg/runtime/NMT/JcmdWithNMTDisabled.java +++ b/test/hotspot/jtreg/runtime/NMT/JcmdWithNMTDisabled.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -77,6 +77,7 @@ public static void main(String args[]) throws Exception { jcmdCommand("summary.diff"); jcmdCommand("detail.diff"); jcmdCommand("scale=GB"); + jcmdCommand("format=xml"); } // Helper method for invoking different jcmd calls, all should fail with the same message saying NMT is not enabled diff --git a/test/hotspot/jtreg/runtime/NMT/NMTTestUtils.java b/test/hotspot/jtreg/runtime/NMT/NMTTestUtils.java index 004f8604323a4..e2fdead4116cf 100644 --- a/test/hotspot/jtreg/runtime/NMT/NMTTestUtils.java +++ b/test/hotspot/jtreg/runtime/NMT/NMTTestUtils.java @@ -35,6 +35,7 @@ public static OutputAnalyzer startJcmdVMNativeMemory(String... additional_args) String fullargs[] = StringArrayUtils.concat("VM.native_memory", additional_args); ProcessBuilder pb = new ProcessBuilder(); pb.command(new PidJcmdExecutor().getCommandLine(fullargs)); + pb.start().waitFor(); OutputAnalyzer output = new OutputAnalyzer(pb.start()); return output; } diff --git a/test/hotspot/jtreg/runtime/NMT/NMTXmlUtils.java b/test/hotspot/jtreg/runtime/NMT/NMTXmlUtils.java new file mode 100644 index 0000000000000..16a186fbacd82 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/NMTXmlUtils.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2025, 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. + */ + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; + +public class NMTXmlUtils { + + private static final String RESERVED_CURRENT_OF_TEST_SEARCH = + // get reservedCurrent of Test tag + "/nativeMemoryTracking/memoryTag[name[text() = 'Test']]/vmDiff/reservedCurrent/text()"; + private static final String RESERVED_DIFF_OF_TEST_SEARCH = + "/nativeMemoryTracking/memoryTag[name[text() = 'Test']]/vmDiff/reservedDiff/text()"; + private static final String COMMITTED_CURRENT_OF_TEST_SEARCH = + "/nativeMemoryTracking/memoryTag[name[text() = 'Test']]/vmDiff/committedCurrent/text()"; + private static final String COMMITTED_DIFF_OF_TEST_SEARCH = + "/nativeMemoryTracking/memoryTag[name[text() = 'Test']]/vmDiff/committedDiff/text()"; + private static final String TEST_TAG_SEARCH = + "/nativeMemoryTracking/memoryTag[name[text() = 'Test']]"; + private static final String SCALE_ATTR_SEARCH = + "/nativeMemoryTracking/@scale"; + private static final String REPORT_TYPE_SEARCH = + "/nativeMemoryTracking/report/text()"; + private static final String CLASSES_COUNT_SEARCH = + "/nativeMemoryTracking/memoryTags/memoryTag/classes/text()"; + private static final String INSTANCE_CLASSES_COUNT_SEARCH = + "/nativeMemoryTracking/memoryTags/memoryTag/instanceClasses/text()"; + private static final String ARRAY_CLASSES_COUNT_SEARCH = + "/nativeMemoryTracking/memoryTags/memoryTag/arrayClasses/text()"; + private static final String GENERAL_STATISTICS_PREINIT_STATE_SEARCH = + "/nativeMemoryTracking/state/preinitState"; + private static final String GENERAL_STATISTICS_MALLOC_LIMIT_SEARCH = + "/nativeMemoryTracking/state/mallocLimit"; + private static final String DETAIL_STATISTICS_TABLE_SIZE_SEARCH = + "/nativeMemoryTracking/state/tableSize"; + private static final String DETAIL_STATISTICS_STACK_DEPTH_SEARCH = + "/nativeMemoryTracking/state/stackDepth"; + private static final String DETAIL_STATISTICS_SITE_TABLE_SEARCH = + "/nativeMemoryTracking/state/siteTable"; + + private Document doc; + + private static void dropNonXMLParts(File inputXmlFile) throws IOException { + String fileName = inputXmlFile.getAbsolutePath(); + Path originalPath = Paths.get(fileName); + Path tempPath = Paths.get(fileName + ".tmp"); + + + // This flag is used to skip the first line we read + boolean firstLineSkipped = false; + + BufferedReader reader = new BufferedReader(new FileReader(inputXmlFile)); + PrintWriter writer = new PrintWriter(new FileWriter(tempPath.toFile())); + + String currentLine; + + // Read the file line by line + while ((currentLine = reader.readLine()) != null) { + if (!firstLineSkipped) { + // Skip the first line and set the flag + firstLineSkipped = true; + continue; // Skip writing the line + } + + // Write all subsequent lines to the temporary file + writer.println(currentLine); + } + reader.close(); + writer.close(); + Files.move(tempPath, originalPath, StandardCopyOption.REPLACE_EXISTING); + System.out.println("✅ Successfully removed the first line from " + fileName); + } + + public NMTXmlUtils(File xmlFile) throws Exception { + NMTXmlUtils.dropNonXMLParts(xmlFile); + doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile); + } + + public NMTXmlUtils shouldHaveValue(String xpathSearch, String expectedValue) throws Exception { + XPath xpath = XPathFactory.newInstance().newXPath(); + + String value = (String)xpath.compile(xpathSearch).evaluate(doc, XPathConstants.STRING); + if (!value.equals(expectedValue)) { + throw new RuntimeException("Mismatch for " + xpathSearch + " expected " + expectedValue + " got " + value); + } + return this; + } + + public NMTXmlUtils shouldExist(String xpathSearch) throws Exception { + XPath xpath = XPathFactory.newInstance().newXPath(); + + String value = (String)xpath.compile(xpathSearch).evaluate(doc, XPathConstants.STRING); + if (value.isEmpty()) { + throw new RuntimeException("No value for " + xpathSearch); + } + return this; + } + + public NMTXmlUtils shouldNotExist(String xpathSearch) throws Exception { + shouldHaveValue(xpathSearch, ""); + return this; + } + + public NMTXmlUtils shouldBeReservedCurrentOfTest(String value) throws Exception { + shouldHaveValue(RESERVED_CURRENT_OF_TEST_SEARCH, value); + return this; + } + + public NMTXmlUtils shouldBeReservedDiffOfTest(String value) throws Exception { + shouldHaveValue(RESERVED_DIFF_OF_TEST_SEARCH, value); + return this; + } + + public NMTXmlUtils shouldBeCommittedCurrentOfTest(String value) throws Exception { + shouldHaveValue(COMMITTED_CURRENT_OF_TEST_SEARCH, value); + return this; + } + + public NMTXmlUtils shouldBeCommittedDiffOfTest(String value) throws Exception { + shouldHaveValue(COMMITTED_DIFF_OF_TEST_SEARCH, value); + return this; + } + + public NMTXmlUtils shouldNotExistTestTag() throws Exception { + shouldNotExist(TEST_TAG_SEARCH); + return this; + } + + public NMTXmlUtils shouldBeReportType(String reportType) throws Exception { + shouldHaveValue(REPORT_TYPE_SEARCH, reportType); + return this; + } + + public NMTXmlUtils shouldBeScale(String scale) throws Exception { + shouldHaveValue(SCALE_ATTR_SEARCH, scale); + return this; + } + + public NMTXmlUtils shouldExistClasses() throws Exception { + shouldExist(CLASSES_COUNT_SEARCH); + return this; + } + + public NMTXmlUtils shouldExistInstanceClasses() throws Exception { + shouldExist(INSTANCE_CLASSES_COUNT_SEARCH); + return this; + } + + public NMTXmlUtils shouldExistArrayClasses() throws Exception { + shouldExist(ARRAY_CLASSES_COUNT_SEARCH); + return this; + } + + public NMTXmlUtils shouldExistGeneralStatistics() throws Exception { + shouldExist(GENERAL_STATISTICS_PREINIT_STATE_SEARCH); + shouldExist(GENERAL_STATISTICS_MALLOC_LIMIT_SEARCH); + return this; + } + + public NMTXmlUtils shouldExistDetailStatistics() throws Exception { + shouldExist(DETAIL_STATISTICS_TABLE_SIZE_SEARCH); + shouldExist(DETAIL_STATISTICS_STACK_DEPTH_SEARCH); + shouldExist(DETAIL_STATISTICS_SITE_TABLE_SEARCH); + return this; + } +}