diff --git a/src/hotspot/share/c1/c1_Compilation.cpp b/src/hotspot/share/c1/c1_Compilation.cpp index ed7519febe8..864510da371 100644 --- a/src/hotspot/share/c1/c1_Compilation.cpp +++ b/src/hotspot/share/c1/c1_Compilation.cpp @@ -33,7 +33,10 @@ #include "c1/c1_ValueMap.hpp" #include "c1/c1_ValueStack.hpp" #include "code/debugInfoRec.hpp" +#include "compiler/compilationMemoryStatistic.hpp" +#include "compiler/compilerDirectives.hpp" #include "compiler/compileLog.hpp" +#include "compiler/compileTask.hpp" #include "compiler/compilerDirectives.hpp" #include "memory/resourceArea.hpp" #include "runtime/sharedRuntime.hpp" @@ -442,6 +445,9 @@ void Compilation::install_code(int frame_size) { void Compilation::compile_method() { + + CompilationMemoryStatisticMark cmsm(env()->task()->directive()); + { PhaseTraceTime timeit(_t_setup); diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index 6162be7c0fe..4410d1c61d6 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -219,7 +219,7 @@ void SymbolTable::create_table () { if (symbol_alloc_arena_size == 0) { _arena = new (mtSymbol) Arena(mtSymbol); } else { - _arena = new (mtSymbol) Arena(mtSymbol, symbol_alloc_arena_size); + _arena = new (mtSymbol) Arena(mtSymbol, Arena::Tag::tag_other, symbol_alloc_arena_size); } } diff --git a/src/hotspot/share/compiler/compilationMemoryStatistic.cpp b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp new file mode 100644 index 00000000000..182ab9fd2ed --- /dev/null +++ b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp @@ -0,0 +1,451 @@ +/* + * 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 "logging/log.hpp" +#include "logging/logStream.hpp" +#include "compiler/abstractCompiler.hpp" +#include "compiler/compilationMemoryStatistic.hpp" +#include "compiler/compilerDirectives.hpp" +#include "compiler/compileTask.hpp" +#include "compiler/compilerDefinitions.hpp" +#include "compiler/compilerThread.hpp" +#include "memory/arena.hpp" +#include "memory/resourceArea.hpp" +#include "oops/symbol.hpp" +#ifdef COMPILER2 +#include "opto/node.hpp" // compile.hpp is not self-contained +#include "opto/compile.hpp" +#endif +#include "services/nmtCommon.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" +#include "utilities/quickSort.hpp" +#include "utilities/resourceHash.hpp" + + +ArenaStatCounter::ArenaStatCounter() : + _current(0), _start(0), _peak(0), + _na(0), _ra(0), + _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0) +{} + +size_t ArenaStatCounter::peak_since_start() const { + return _peak > _start ? _peak - _start : 0; +} + +void ArenaStatCounter::start() { + _peak = _start = _current; +} + +void ArenaStatCounter::update_c2_node_count() { +#ifdef COMPILER2 + CompilerThread* const th = Thread::current()->as_Compiler_thread(); + const CompileTask* const task = th->task(); + if (task != nullptr && + th->task()->compiler() != nullptr && + th->task()->compiler()->type() == compiler_c2) { + const Compile* const comp = Compile::current(); + if (comp != nullptr) { + _live_nodes_at_peak = comp->live_nodes(); + } + } +#endif +} + +// Account an arena allocation or de-allocation. +bool ArenaStatCounter::account(ssize_t delta, int tag) { + bool rc = false; +#ifdef ASSERT + // Note: if this fires, we free more arena memory under the scope of the + // CompilationMemoryHistoryMark than we allocate. This cannot be since we + // assume arena allocations in CompilerThread to be stack bound and symmetric. + assert(delta >= 0 || ((ssize_t)_current + delta) >= 0, + "Negative overflow (d=%zd %zu %zu %zu)", delta, _current, _start, _peak); +#endif + // Update totals + _current += delta; + // Update detail counter + switch ((Arena::Tag)tag) { + case Arena::Tag::tag_ra: _ra += delta; break; + case Arena::Tag::tag_node: _na += delta; break; + default: // ignore + break; + }; + // Did we reach a peak? + if (_current > _peak) { + _peak = _current; + assert(delta > 0, "Sanity (%zu %zu %zu)", _current, _start, _peak); + _na_at_peak = _na; + _ra_at_peak = _ra; + update_c2_node_count(); + rc = true; + } + return rc; +} + +void ArenaStatCounter::print_on(outputStream* st) const { + st->print("%zu [na %zu ra %zu]", peak_since_start(), _na_at_peak, _ra_at_peak); +#ifdef ASSERT + st->print(" (%zu->%zu->%zu)", _start, _peak, _current); +#endif +} + +////////////////////////// +// Backend + +class FullMethodName { + Symbol* const _k; + Symbol* const _m; + Symbol* const _s; + +public: + + FullMethodName(Symbol* k, Symbol* m, Symbol* s) : _k(k), _m(m), _s(s) {} + FullMethodName(const FullMethodName& o) : _k(o._k), _m(o._m), _s(o._s) {} + + void make_permanent() { + _k->make_permanent(); + _m->make_permanent(); + _s->make_permanent(); + } + + static unsigned compute_hash(const FullMethodName& n) { + return Symbol::compute_hash(n._k) ^ + Symbol::compute_hash(n._m) ^ + Symbol::compute_hash(n._s); + } + + char* as_C_string(char* buf, size_t len) const { + stringStream ss(buf, len); + ResourceMark rm; + ss.print_raw(_k->as_C_string()); + ss.print_raw("::"); + ss.print_raw(_m->as_C_string()); + ss.put('('); + ss.print_raw(_s->as_C_string()); + ss.put(')'); + return buf; + } + + bool equals(const FullMethodName& b) const { + return _k == b._k && _m == b._m && _s == b._s; + } + + bool operator== (const FullMethodName& other) const { return equals(other); } +}; + +// Note: not mtCompiler since we don't want to change what we measure +class MemStatEntry : public CHeapObj { + const FullMethodName _method; + CompilerType _comptype; + double _time; + // How often this has been recompiled. + int _num_recomp; + // Compiling thread. Only for diagnostic purposes. Thread may not be alive anymore. + const Thread* _thread; + + size_t _total; + size_t _na_at_peak; + size_t _ra_at_peak; + unsigned _live_nodes_at_peak; + +public: + + MemStatEntry(FullMethodName method) + : _method(method), _comptype(compiler_c1), + _time(0), _num_recomp(0), _thread(nullptr), + _total(0), _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0) { + } + + void set_comptype(CompilerType comptype) { _comptype = comptype; } + void set_current_time() { _time = os::elapsedTime(); } + void set_current_thread() { _thread = Thread::current(); } + void inc_recompilation() { _num_recomp++; } + + void set_total(size_t n) { _total = n; } + void set_na_at_peak(size_t n) { _na_at_peak = n; } + void set_ra_at_peak(size_t n) { _ra_at_peak = n; } + void set_live_nodes_at_peak(unsigned n) { _live_nodes_at_peak = n; } + + size_t total() const { return _total; } + + static void print_legend(outputStream* st) { + st->print_cr("Legend:"); + st->print_cr(" total : memory allocated via arenas while compiling"); + st->print_cr(" NA : ...how much in node arenas (if c2)"); + st->print_cr(" RA : ...how much in resource areas"); + st->print_cr(" #nodes : ...how many nodes (if c2)"); + st->print_cr(" time : time of last compilation (sec)"); + st->print_cr(" type : compiler type"); + st->print_cr(" #rc : how often recompiled"); + st->print_cr(" thread : compiler thread"); + } + + static void print_header(outputStream* st) { + st->print_cr("total NA RA #nodes time type #rc thread method"); + } + + void print_on(outputStream* st, bool human_readable) const { + int col = 0; + + // Total + if (human_readable) { + st->print(PROPERFMT " ", PROPERFMTARGS(_total)); + } else { + st->print("%zu ", _total); + } + col += 10; st->fill_to(col); + + // NA + if (human_readable) { + st->print(PROPERFMT " ", PROPERFMTARGS(_na_at_peak)); + } else { + st->print("%zu ", _na_at_peak); + } + col += 10; st->fill_to(col); + + // RA + if (human_readable) { + st->print(PROPERFMT " ", PROPERFMTARGS(_ra_at_peak)); + } else { + st->print("%zu ", _ra_at_peak); + } + col += 10; st->fill_to(col); + + // Number of Nodes when memory peaked + st->print("%u ", _live_nodes_at_peak); + col += 8; st->fill_to(col); + + // TimeStamp + st->print("%.3f ", _time); + col += 8; st->fill_to(col); + + // Type + st->print("%s ", compilertype2name(_comptype)); + col += 6; st->fill_to(col); + + // Recomp + st->print("%u ", _num_recomp); + col += 4; st->fill_to(col); + + // Thread + st->print(PTR_FORMAT " ", p2i(_thread)); + + // MethodName + char buf[1024]; + st->print("%s ", _method.as_C_string(buf, sizeof(buf))); + st->cr(); + } + + int compare_by_size(const MemStatEntry* b) const { + const size_t x1 = b->_total; + const size_t x2 = _total; + return x1 < x2 ? -1 : x1 == x2 ? 0 : 1; + } + + bool equals(const FullMethodName& b) const { + return _method.equals(b); + } +}; + +class MemStatTable : + public ResourceHashtable +{ +public: + + void add(const FullMethodName& fmn, CompilerType comptype, + size_t total, size_t na_at_peak, size_t ra_at_peak, + unsigned live_nodes_at_peak) { + assert_lock_strong(NMTCompilationCostHistory_lock); + + MemStatEntry** pe = get(fmn); + MemStatEntry* e = nullptr; + if (pe == nullptr) { + e = new MemStatEntry(fmn); + put(fmn, e); + } else { + // Update existing entry + e = *pe; + assert(e != nullptr, "Sanity"); + } + e->set_current_time(); + e->set_current_thread(); + e->set_comptype(comptype); + e->inc_recompilation(); + e->set_total(total); + e->set_na_at_peak(na_at_peak); + e->set_ra_at_peak(ra_at_peak); + e->set_live_nodes_at_peak(live_nodes_at_peak); + } + + // Returns a C-heap-allocated SortMe array containing all entries from the table, + // optionally filtered by entry size + MemStatEntry** calc_flat_array(int& num, size_t min_size) { + assert_lock_strong(NMTCompilationCostHistory_lock); + + const int num_all = number_of_entries(); + MemStatEntry** flat = NEW_C_HEAP_ARRAY(MemStatEntry*, num_all, mtInternal); + int i = 0; + auto do_f = [&] (const FullMethodName& ignored, MemStatEntry* e) { + if (e->total() >= min_size) { + flat[i] = e; + assert(i < num_all, "Sanity"); + i ++; + } + }; + iterate_all(do_f); + if (min_size == 0) { + assert(i == num_all, "Sanity"); + } else { + assert(i <= num_all, "Sanity"); + } + num = i; + return flat; + } +}; + +bool CompilationMemoryStatistic::_enabled = false; + +static MemStatTable* _the_table = nullptr; + +void CompilationMemoryStatistic::initialize() { + assert(_enabled == false && _the_table == nullptr, "Only once"); + _the_table = new (mtCompiler) MemStatTable; + _enabled = true; + log_info(compilation, alloc)("Compilation memory statistic enabled"); +} + +void CompilationMemoryStatistic::on_start_compilation() { + assert(enabled(), "Not enabled?"); + Thread::current()->as_Compiler_thread()->arena_stat()->start(); +} + +void CompilationMemoryStatistic::on_end_compilation() { + assert(enabled(), "Not enabled?"); + ResourceMark rm; + CompilerThread* const th = Thread::current()->as_Compiler_thread(); + const ArenaStatCounter* const arena_stat = th->arena_stat(); + const CompilerType ct = th->task()->compiler()->type(); + + const Method* const m = th->task()->method(); + FullMethodName fmn(m->klass_name(), m->name(), m->signature()); + fmn.make_permanent(); + + const DirectiveSet* directive = th->task()->directive(); + assert(directive->should_collect_memstat(), "Only call if memstat is enabled"); + const bool print = directive->should_print_memstat(); + + if (print) { + char buf[1024]; + fmn.as_C_string(buf, sizeof(buf)); + tty->print("%s Arena usage %s: ", compilertype2name(ct), buf); + arena_stat->print_on(tty); + tty->cr(); + } + { + MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag); + assert(_the_table != nullptr, "not initialized"); + + _the_table->add(fmn, ct, + arena_stat->peak_since_start(), // total + arena_stat->na_at_peak(), + arena_stat->ra_at_peak(), + arena_stat->live_nodes_at_peak()); + } +} + +void CompilationMemoryStatistic::on_arena_change(ssize_t diff, const Arena* arena) { + assert(enabled(), "Not enabled?"); + CompilerThread* const th = Thread::current()->as_Compiler_thread(); + th->arena_stat()->account(diff, (int)arena->get_tag()); +} + +static inline ssize_t diff_entries_by_size(const MemStatEntry* e1, const MemStatEntry* e2) { + return e1->compare_by_size(e2); +} + +void CompilationMemoryStatistic::print_all_by_size(outputStream* st, bool human_readable, size_t min_size) { + st->print_cr("Compilation memory statistics"); + + if (!enabled()) { + st->print_cr("(unavailable)"); + return; + } + + st->cr(); + + MemStatEntry::print_legend(st); + st->cr(); + + if (min_size > 0) { + st->print_cr(" (cutoff: %zu bytes)", min_size); + } + st->cr(); + + MemStatEntry::print_header(st); + + MemStatEntry** filtered = nullptr; + { + MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag); + + if (_the_table != nullptr) { + // We sort with quicksort + int num = 0; + filtered = _the_table->calc_flat_array(num, min_size); + if (min_size > 0) { + st->print_cr("(%d/%d)", num, _the_table->number_of_entries()); + } + if (num > 0) { + QuickSort::sort(filtered, num, diff_entries_by_size, false); + // Now print. Has to happen under lock protection too, since entries may be changed. + for (int i = 0; i < num; i ++) { + filtered[i]->print_on(st, human_readable); + } + } else { + st->print_cr("No entries."); + } + } else { + st->print_cr("Not initialized."); + } + } // locked + + FREE_C_HEAP_ARRAY(Entry, filtered); +} + +CompilationMemoryStatisticMark::CompilationMemoryStatisticMark(const DirectiveSet* directive) + : _active(directive->should_collect_memstat()) { + if (_active) { + CompilationMemoryStatistic::on_start_compilation(); + } +} +CompilationMemoryStatisticMark::~CompilationMemoryStatisticMark() { + if (_active) { + CompilationMemoryStatistic::on_end_compilation(); + } +} diff --git a/src/hotspot/share/compiler/compilationMemoryStatistic.hpp b/src/hotspot/share/compiler/compilationMemoryStatistic.hpp new file mode 100644 index 00000000000..06ac9382199 --- /dev/null +++ b/src/hotspot/share/compiler/compilationMemoryStatistic.hpp @@ -0,0 +1,103 @@ +/* + * 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_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP +#define SHARE_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP + +#include "compiler/compilerDefinitions.hpp" +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; +class Symbol; +class DirectiveSet; + +// Counters for allocations from one arena +class ArenaStatCounter : public CHeapObj { + // Current bytes, total + size_t _current; + // bytes when compilation started + size_t _start; + // bytes at last peak, total + size_t _peak; + // Current bytes used for node arenas, total + size_t _na; + // Current bytes used for resource areas + size_t _ra; + + // Peak composition: + // Size of node arena when total peaked (c2 only) + size_t _na_at_peak; + // Size of resource area when total peaked + size_t _ra_at_peak; + // Number of live nodes when total peaked (c2 only) + unsigned _live_nodes_at_peak; + + void update_c2_node_count(); + +public: + ArenaStatCounter(); + + // Size of peak since last compilation + size_t peak_since_start() const; + + // Peak details + size_t na_at_peak() const { return _na_at_peak; } + size_t ra_at_peak() const { return _ra_at_peak; } + unsigned live_nodes_at_peak() const { return _live_nodes_at_peak; } + + // Mark the start of a compilation. + void start(); + + // Account an arena allocation or de-allocation. + // Returns true if new peak reached + bool account(ssize_t delta, int tag); + + void set_live_nodes_at_peak(unsigned i) { _live_nodes_at_peak = i; } + void print_on(outputStream* st) const; +}; + +class CompilationMemoryStatistic : public AllStatic { + static bool _enabled; +public: + static void initialize(); + // true if CollectMemStat or PrintMemStat has been enabled for any method + static bool enabled() { return _enabled; } + static void on_start_compilation(); + static void on_end_compilation(); + static void on_arena_change(ssize_t diff, const Arena* arena); + static void print_all_by_size(outputStream* st, bool human_readable, size_t minsize); +}; + +// RAII object to wrap one compilation +class CompilationMemoryStatisticMark { + const bool _active; +public: + CompilationMemoryStatisticMark(const DirectiveSet* directive); + ~CompilationMemoryStatisticMark(); +}; + +#endif // SHARE_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index 723e7a08cc0..08c61095d47 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -31,6 +31,7 @@ #include "code/codeHeapState.hpp" #include "code/dependencyContext.hpp" #include "compiler/compilationLog.hpp" +#include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compilationPolicy.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileLog.hpp" @@ -650,6 +651,10 @@ void CompileBroker::compilation_init(JavaThread* THREAD) { } #endif // INCLUDE_JVMCI + if (CompilerOracle::should_collect_memstat()) { + CompilationMemoryStatistic::initialize(); + } + // Start the compiler thread(s) init_compiler_threads(); // totalTime performance counter is always created as it is required diff --git a/src/hotspot/share/compiler/compilerDefinitions.hpp b/src/hotspot/share/compiler/compilerDefinitions.hpp index 4e8b55f1a36..6321e3e0738 100644 --- a/src/hotspot/share/compiler/compilerDefinitions.hpp +++ b/src/hotspot/share/compiler/compilerDefinitions.hpp @@ -38,7 +38,7 @@ enum CompilerType : u1 { }; extern const char* compilertype2name_tab[compiler_number_of_types]; // Map CompilerType to its name -inline const char* compilertype2name(CompilerType t) { return (uint)t < compiler_number_of_types ? compilertype2name_tab[t] : nullptr; } +inline const char* compilertype2name(CompilerType t) { return (uint)t < compiler_number_of_types ? compilertype2name_tab[t] : "invalid"; } // Handy constants for deciding which compiler mode to use. enum MethodCompilation { diff --git a/src/hotspot/share/compiler/compilerDirectives.cpp b/src/hotspot/share/compiler/compilerDirectives.cpp index a15f9974f5e..0368a6ad744 100644 --- a/src/hotspot/share/compiler/compilerDirectives.cpp +++ b/src/hotspot/share/compiler/compilerDirectives.cpp @@ -202,6 +202,14 @@ bool DirectiveSet::is_c2(CompilerDirectives* directive) const { return this == directive->_c2_store; } +bool DirectiveSet::should_collect_memstat() const { + return MemStatOption > 0; +} + +bool DirectiveSet::should_print_memstat() const { + return MemStatOption == (uintx)MemStatAction::print; +} + // In the list of Control/disabled intrinsics, the ID of the control intrinsics can separated: // - by ',' (if -XX:Control/DisableIntrinsic is used once when invoking the VM) or // - by '\n' (if -XX:Control/DisableIntrinsic is used multiple times when invoking the VM) or diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index f05bec6a104..8c8dcb99f0d 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -41,6 +41,7 @@ cflags(BreakAtExecute, bool, false, BreakAtExecute) \ cflags(BreakAtCompile, bool, false, BreakAtCompile) \ cflags(Log, bool, LogCompilation, Unknown) \ + cflags(MemStat, uintx, 0, MemStat) \ cflags(PrintAssembly, bool, PrintAssembly, PrintAssembly) \ cflags(PrintCompilation, bool, PrintCompilation, PrintCompilation) \ cflags(PrintInlining, bool, PrintInlining, PrintInlining) \ @@ -131,6 +132,8 @@ public: void finalize(outputStream* st); bool is_c1(CompilerDirectives* directive) const; bool is_c2(CompilerDirectives* directive) const; + bool should_collect_memstat() const; + bool should_print_memstat() const; typedef enum { #define enum_of_flags(name, type, dvalue, cc_flag) name##Index, diff --git a/src/hotspot/share/compiler/compilerOracle.cpp b/src/hotspot/share/compiler/compilerOracle.cpp index 031e9b25b7a..e1fb019bdbf 100644 --- a/src/hotspot/share/compiler/compilerOracle.cpp +++ b/src/hotspot/share/compiler/compilerOracle.cpp @@ -101,6 +101,7 @@ class TypedMethodOptionMatcher; static TypedMethodOptionMatcher* option_list = nullptr; static bool any_set = false; +static bool print_final_memstat_report = false; // A filter for quick lookup if an option is set static bool option_filter[static_cast(CompileCommand::Unknown) + 1] = { 0 }; @@ -325,6 +326,7 @@ static void register_command(TypedMethodOptionMatcher* matcher, tty->print("CompileCommand: %s ", option2name(option)); matcher->print(); } + return; } @@ -455,6 +457,15 @@ bool CompilerOracle::should_print_methods() { return has_command(CompileCommand::Print); } +// Tells whether there are any methods to collect memory statistics for +bool CompilerOracle::should_collect_memstat() { + return has_command(CompileCommand::MemStat); +} + +bool CompilerOracle::should_print_final_memstat_report() { + return print_final_memstat_report; +} + bool CompilerOracle::should_log(const methodHandle& method) { if (!LogCompilation) return false; if (!has_command(CompileCommand::Log)) { @@ -623,6 +634,22 @@ void skip_comma(char* &line) { } } +static bool parseEnumValueAsUintx(enum CompileCommand option, const char* line, uintx& value, int& bytes_read, char* errorbuf, const int buf_size) { + if (option == CompileCommand::MemStat) { + if (strncasecmp(line, "collect", 7) == 0) { + value = (uintx)MemStatAction::collect; + } else if (strncasecmp(line, "print", 5) == 0) { + value = (uintx)MemStatAction::print; + print_final_memstat_report = true; + } else { + jio_snprintf(errorbuf, buf_size, "MemStat: invalid value expected 'collect' or 'print' (omitting value means 'collect')"); + } + return true; // handled + } + return false; +#undef HANDLE_VALUE +} + static void scan_value(enum OptionType type, char* line, int& total_bytes_read, TypedMethodOptionMatcher* matcher, enum CompileCommand option, char* errorbuf, const int buf_size) { int bytes_read = 0; @@ -642,7 +669,13 @@ static void scan_value(enum OptionType type, char* line, int& total_bytes_read, } } else if (type == OptionType::Uintx) { uintx value; - if (sscanf(line, "" UINTX_FORMAT "%n", &value, &bytes_read) == 1) { + // Is it a named enum? + bool success = parseEnumValueAsUintx(option, line, value, bytes_read, errorbuf, buf_size); + if (!success) { + // Is it a raw number? + success = (sscanf(line, "" UINTX_FORMAT "%n", &value, &bytes_read) == 1); + } + if (success) { total_bytes_read += bytes_read; line += bytes_read; register_command(matcher, option, value); @@ -914,10 +947,14 @@ bool CompilerOracle::parse_from_line(char* line) { } skip_whitespace(line); if (*line == '\0') { - // if this is a bool option this implies true if (option2type(option) == OptionType::Bool) { + // if this is a bool option this implies true register_command(matcher, option, true); return true; + } else if (option == CompileCommand::MemStat) { + // MemStat default action is to collect data but to not print + register_command(matcher, option, (uintx)MemStatAction::collect); + return true; } else { jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option)); print_parse_error(error_buf, original.get()); diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index 1de323cae01..251f29fda38 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -57,6 +57,7 @@ class methodHandle; option(Break, "break", Bool) \ option(BreakAtExecute, "BreakAtExecute", Bool) \ option(BreakAtCompile, "BreakAtCompile", Bool) \ + option(MemStat, "MemStat", Uintx) \ option(PrintAssembly, "PrintAssembly", Bool) \ option(PrintCompilation, "PrintCompilation", Bool) \ option(PrintInlining, "PrintInlining", Bool) \ @@ -113,6 +114,10 @@ enum class OptionType { Unknown }; +enum class MemStatAction { + collect = 1, print = 2 +}; + class CompilerOracle : AllStatic { private: static bool _quiet; @@ -151,6 +156,10 @@ class CompilerOracle : AllStatic { // Tells whether there are any methods to print for print_method_statistics() static bool should_print_methods(); + // Tells whether there are any methods to (collect|collect+print) memory statistics for + static bool should_collect_memstat(); + static bool should_print_final_memstat_report(); + // Tags the method as blackhole candidate, if possible. static void tag_blackhole_if_possible(const methodHandle& method); diff --git a/src/hotspot/share/compiler/compilerThread.cpp b/src/hotspot/share/compiler/compilerThread.cpp index ddd49fec175..77a5f4b7027 100644 --- a/src/hotspot/share/compiler/compilerThread.cpp +++ b/src/hotspot/share/compiler/compilerThread.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileTask.hpp" #include "compiler/compilerThread.hpp" @@ -39,6 +40,7 @@ CompilerThread::CompilerThread(CompileQueue* queue, _counters = counters; _buffer_blob = nullptr; _compiler = nullptr; + _arena_stat = CompilationMemoryStatistic::enabled() ? new ArenaStatCounter : nullptr; // Compiler uses resource area for compilation, let's bias it to mtCompiler resource_area()->bias_to(mtCompiler); @@ -51,6 +53,7 @@ CompilerThread::CompilerThread(CompileQueue* queue, CompilerThread::~CompilerThread() { // Delete objects which were allocated on heap. delete _counters; + delete _arena_stat; } void CompilerThread::thread_entry(JavaThread* thread, TRAPS) { diff --git a/src/hotspot/share/compiler/compilerThread.hpp b/src/hotspot/share/compiler/compilerThread.hpp index 3ae4bef3dad..65bb4481c02 100644 --- a/src/hotspot/share/compiler/compilerThread.hpp +++ b/src/hotspot/share/compiler/compilerThread.hpp @@ -27,8 +27,9 @@ #include "runtime/javaThread.hpp" -class BufferBlob; class AbstractCompiler; +class ArenaStatCounter; +class BufferBlob; class ciEnv; class CompileThread; class CompileLog; @@ -54,6 +55,8 @@ class CompilerThread : public JavaThread { AbstractCompiler* _compiler; TimeStamp _idle_time; + ArenaStatCounter* _arena_stat; + public: static CompilerThread* current() { @@ -81,6 +84,7 @@ class CompilerThread : public JavaThread { CompileQueue* queue() const { return _queue; } CompilerCounters* counters() const { return _counters; } + ArenaStatCounter* arena_stat() const { return _arena_stat; } // Get/set the thread's compilation environment. ciEnv* env() { return _env; } diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index ac16279ab71..f767255116c 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -24,6 +24,7 @@ */ #include "precompiled.hpp" +#include "compiler/compilationMemoryStatistic.hpp" #include "memory/allocation.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" @@ -209,7 +210,7 @@ void Chunk::next_chop(Chunk* k) { k->_next = nullptr; } -Arena::Arena(MEMFLAGS flag, size_t init_size) : _flags(flag), _size_in_bytes(0) { +Arena::Arena(MEMFLAGS flag, Tag tag, size_t init_size) : _flags(flag), _tag(tag), _size_in_bytes(0) { init_size = ARENA_ALIGN(init_size); _chunk = ChunkPool::allocate_chunk(init_size, AllocFailStrategy::EXIT_OOM); _first = _chunk; @@ -219,7 +220,7 @@ Arena::Arena(MEMFLAGS flag, size_t init_size) : _flags(flag), _size_in_bytes(0) set_size_in_bytes(init_size); } -Arena::Arena(MEMFLAGS flag) : _flags(flag), _size_in_bytes(0) { +Arena::Arena(MEMFLAGS flag, Tag tag) : _flags(flag), _tag(tag), _size_in_bytes(0) { _chunk = ChunkPool::allocate_chunk(Chunk::init_size, AllocFailStrategy::EXIT_OOM); _first = _chunk; _hwm = _chunk->bottom(); // Save the cached hwm, max @@ -251,6 +252,12 @@ void Arena::set_size_in_bytes(size_t size) { ssize_t delta = size - size_in_bytes(); _size_in_bytes = size; MemTracker::record_arena_size_change(delta, _flags); + if (CompilationMemoryStatistic::enabled() && _flags == mtCompiler) { + Thread* const t = Thread::current(); + if (t != nullptr && t->is_Compiler_thread()) { + CompilationMemoryStatistic::on_arena_change(delta, this); + } + } } } diff --git a/src/hotspot/share/memory/arena.hpp b/src/hotspot/share/memory/arena.hpp index ad142e189ac..d5af068ffe1 100644 --- a/src/hotspot/share/memory/arena.hpp +++ b/src/hotspot/share/memory/arena.hpp @@ -86,13 +86,22 @@ public: // Fast allocation of memory class Arena : public CHeapObjBase { +public: + + enum class Tag { + tag_other = 0, + tag_ra, // resource area + tag_ha, // handle area + tag_node // C2 Node arena + }; + protected: friend class HandleMark; friend class NoHandleMark; friend class VMStructs; MEMFLAGS _flags; // Memory tracking flags - + const Tag _tag; Chunk* _first; // First chunk Chunk* _chunk; // current chunk char* _hwm; // High water mark @@ -115,9 +124,8 @@ protected: public: // Start the chunk_pool cleaner task static void start_chunk_pool_cleaner_task(); - - Arena(MEMFLAGS memflag); - Arena(MEMFLAGS memflag, size_t init_size); + Arena(MEMFLAGS memflag, Tag tag = Tag::tag_other); + Arena(MEMFLAGS memflag, Tag tag, size_t init_size); ~Arena(); void destruct_contents(); char* hwm() const { return _hwm; } @@ -171,6 +179,8 @@ protected: size_t size_in_bytes() const { return _size_in_bytes; }; void set_size_in_bytes(size_t size); + Tag get_tag() const { return _tag; } + private: // Reset this Arena to empty, access will trigger grow if necessary void reset(void) { diff --git a/src/hotspot/share/memory/resourceArea.hpp b/src/hotspot/share/memory/resourceArea.hpp index b81209a62d1..7d7692c19ea 100644 --- a/src/hotspot/share/memory/resourceArea.hpp +++ b/src/hotspot/share/memory/resourceArea.hpp @@ -26,6 +26,7 @@ #define SHARE_MEMORY_RESOURCEAREA_HPP #include "memory/allocation.hpp" +#include "memory/arena.hpp" #include "runtime/javaThread.hpp" // The resource area holds temporary data structures in the VM. @@ -51,10 +52,11 @@ class ResourceArea: public Arena { public: ResourceArea(MEMFLAGS flags = mtThread) : - Arena(flags) DEBUG_ONLY(COMMA _nesting(0)) {} + Arena(flags, Arena::Tag::tag_ra) DEBUG_ONLY(COMMA _nesting(0)) {} ResourceArea(size_t init_size, MEMFLAGS flags = mtThread) : - Arena(flags, init_size) DEBUG_ONLY(COMMA _nesting(0)) {} + Arena(flags, Arena::Tag::tag_ra, init_size) DEBUG_ONLY(COMMA _nesting(0)) { + } char* allocate_bytes(size_t size, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM); diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 2831889a94d..5e9dfcacafd 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/vmClasses.hpp" +#include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "runtime/handles.inline.hpp" #include "jfr/support/jfrIntrinsics.hpp" @@ -109,6 +110,8 @@ void C2Compiler::initialize() { void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, bool install_code, DirectiveSet* directive) { assert(is_initialized(), "Compiler thread must be initialized"); + CompilationMemoryStatisticMark cmsm(directive); + bool subsume_loads = SubsumeLoads; bool do_escape_analysis = DoEscapeAnalysis; bool do_iterative_escape_analysis = DoEscapeAnalysis; diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 8ea8095413b..32abfad9845 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -646,8 +646,8 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci, _unique(0), _dead_node_count(0), _dead_node_list(comp_arena()), - _node_arena_one(mtCompiler), - _node_arena_two(mtCompiler), + _node_arena_one(mtCompiler, Arena::Tag::tag_node), + _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler), diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index c9cedb99a45..e86217f8de6 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -728,7 +728,7 @@ WB_ENTRY(jint, WB_NMTGetHashSize(JNIEnv* env, jobject o)) WB_END WB_ENTRY(jlong, WB_NMTNewArena(JNIEnv* env, jobject o, jlong init_size)) - Arena* arena = new (mtTest) Arena(mtTest, size_t(init_size)); + Arena* arena = new (mtTest) Arena(mtTest, Arena::Tag::tag_other, size_t(init_size)); return (jlong)arena; WB_END diff --git a/src/hotspot/share/runtime/handles.hpp b/src/hotspot/share/runtime/handles.hpp index b57be73d6b3..7865d32bef2 100644 --- a/src/hotspot/share/runtime/handles.hpp +++ b/src/hotspot/share/runtime/handles.hpp @@ -187,7 +187,7 @@ class HandleArea: public Arena { HandleArea* _prev; // link to outer (older) area public: // Constructor - HandleArea(HandleArea* prev) : Arena(mtThread, Chunk::tiny_size) { + HandleArea(HandleArea* prev) : Arena(mtThread, Tag::tag_ha, Chunk::tiny_size) { debug_only(_handle_mark_nesting = 0); debug_only(_no_handle_mark_nesting = 0); _prev = prev; diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 6f227988748..d075b6cb9c8 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -31,6 +31,7 @@ #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "code/codeCache.hpp" +#include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerOracle.hpp" #include "gc/shared/collectedHeap.hpp" @@ -341,6 +342,10 @@ void print_statistics() { MetaspaceUtils::print_basic_report(tty, 0); } + if (CompilerOracle::should_print_final_memstat_report()) { + CompilationMemoryStatistic::print_all_by_size(tty, false, 0); + } + ThreadsSMRSupport::log_statistics(); } diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index 5a89df9b8b9..cdbeea9a269 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -139,6 +139,7 @@ Mutex* ThreadIdTableCreate_lock = nullptr; Mutex* SharedDecoder_lock = nullptr; Mutex* DCmdFactory_lock = nullptr; Mutex* NMTQuery_lock = nullptr; +Mutex* NMTCompilationCostHistory_lock = nullptr; #if INCLUDE_CDS #if INCLUDE_JVMTI @@ -309,6 +310,7 @@ void mutex_init() { MUTEX_DEFN(SharedDecoder_lock , PaddedMutex , tty-1); MUTEX_DEFN(DCmdFactory_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(NMTQuery_lock , PaddedMutex , safepoint); + MUTEX_DEFN(NMTCompilationCostHistory_lock , PaddedMutex , nosafepoint); #if INCLUDE_CDS #if INCLUDE_JVMTI MUTEX_DEFN(CDSClassFileStream_lock , PaddedMutex , safepoint); diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index a3088267afa..3bce342c251 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -117,6 +117,7 @@ extern Mutex* ThreadIdTableCreate_lock; // Used by ThreadIdTable to laz extern Mutex* SharedDecoder_lock; // serializes access to the decoder during normal (not error reporting) use extern Mutex* DCmdFactory_lock; // serialize access to DCmdFactory information extern Mutex* NMTQuery_lock; // serialize NMT Dcmd queries +extern Mutex* NMTCompilationCostHistory_lock; // guards NMT compilation cost history #if INCLUDE_CDS #if INCLUDE_JVMTI extern Mutex* CDSClassFileStream_lock; // FileMapInfo::open_stream_for_jvmti diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 42885e935f7..cc431e8c900 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -43,6 +43,7 @@ #include "jfr/support/jfrThreadExtension.hpp" #endif +class CompilerThread; class HandleArea; class HandleMark; class ICRefillVerifier; @@ -324,6 +325,12 @@ class Thread: public ThreadShadow { virtual bool is_AttachListener_thread() const { return false; } virtual bool is_monitor_deflation_thread() const { return false; } + // Convenience cast functions + CompilerThread* as_Compiler_thread() const { + assert(is_Compiler_thread(), "Must be compiler thread"); + return (CompilerThread*)this; + } + // Can this thread make Java upcalls virtual bool can_call_java() const { return false; } diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 8d346a5ea48..a54f2ebaa70 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -31,6 +31,8 @@ #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "code/codeCache.hpp" +#include "compiler/compilationMemoryStatistic.hpp" +#include "compiler/compiler_globals.hpp" #include "compiler/compileBroker.hpp" #include "compiler/directivesParser.hpp" #include "gc/shared/gcVMOperations.hpp" @@ -138,6 +140,7 @@ 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)); // Enhanced JMX Agent Support // These commands won't be exported via the DiagnosticCommandMBean until an @@ -1134,3 +1137,17 @@ void ThreadDumpToFileDCmd::dumpToFile(Symbol* name, Symbol* signature, const cha jbyte* addr = typeArrayOop(res)->byte_at_addr(0); output()->print_raw((const char*)addr, ba->length()); } + +CompilationMemoryStatisticDCmd::CompilationMemoryStatisticDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _human_readable("-H", "Human readable format", "BOOLEAN", false, "false"), + _minsize("-s", "Minimum memory size", "MEMORY SIZE", false, "0") { + _dcmdparser.add_dcmd_option(&_human_readable); + _dcmdparser.add_dcmd_option(&_minsize); +} + +void CompilationMemoryStatisticDCmd::execute(DCmdSource source, TRAPS) { + const bool human_readable = _human_readable.value(); + const size_t minsize = _minsize.has_value() ? _minsize.value()._size : 0; + CompilationMemoryStatistic::print_all_by_size(output(), human_readable, minsize); +} diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 685223ac038..06b7e8748dc 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -954,4 +954,28 @@ public: virtual void execute(DCmdSource source, TRAPS); }; +class CompilationMemoryStatisticDCmd: public DCmdWithParser { +protected: + DCmdArgument _human_readable; + DCmdArgument _minsize; +public: + static int num_arguments() { return 2; } + CompilationMemoryStatisticDCmd(outputStream* output, bool heap); + static const char* name() { + return "Compiler.memory"; + } + static const char* description() { + return "Print compilation footprint"; + } + static const char* impact() { + return "Medium: Pause time depends on number of compiled methods"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", nullptr}; + return p; + } + virtual void execute(DCmdSource source, TRAPS); +}; + #endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP diff --git a/test/hotspot/gtest/memory/test_arena.cpp b/test/hotspot/gtest/memory/test_arena.cpp index 35a85508d68..be8bac46861 100644 --- a/test/hotspot/gtest/memory/test_arena.cpp +++ b/test/hotspot/gtest/memory/test_arena.cpp @@ -329,7 +329,7 @@ TEST_VM(Arena, mixed_alignment_allocation) { TEST_VM(Arena, Arena_with_crooked_initial_size) { // Test that an arena with a crooked, not 64-bit aligned initial size // works - Arena ar(mtTest, 4097); + Arena ar(mtTest, Arena::Tag::tag_other, 4097); void* p1 = ar.AmallocWords(BytesPerWord); void* p2 = ar.Amalloc(BytesPerLong); ASSERT_TRUE(is_aligned(p1, BytesPerWord)); @@ -342,7 +342,7 @@ TEST_VM(Arena, Arena_grows_large_unaligned) { // (only possible on 32-bit when allocating with word alignment). // Then we alloc some more. If Arena::grow() does not correctly align, on 32-bit // something should assert at some point. - Arena ar(mtTest, 100); // first chunk is small + Arena ar(mtTest, Arena::Tag::tag_other, 100); // first chunk is small void* p = ar.AmallocWords(Chunk::size + BytesPerWord); // if Arena::grow() misaligns, this asserts // some more allocations for good measure for (int i = 0; i < 100; i ++) { @@ -372,13 +372,13 @@ TEST_VM(Arena, different_chunk_sizes) { for (int i = 0; i < 1000; i ++) { // Unfortunately, Arenas cannot be newed, // so we are left with awkwardly placing a few on the stack. - Arena ar0(mtTest, random_arena_chunk_size()); - Arena ar1(mtTest, random_arena_chunk_size()); - Arena ar2(mtTest, random_arena_chunk_size()); - Arena ar3(mtTest, random_arena_chunk_size()); - Arena ar4(mtTest, random_arena_chunk_size()); - Arena ar5(mtTest, random_arena_chunk_size()); - Arena ar6(mtTest, random_arena_chunk_size()); - Arena ar7(mtTest, random_arena_chunk_size()); + Arena ar0(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar1(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar2(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar3(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar4(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar5(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar6(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); + Arena ar7(mtTest, Arena::Tag::tag_other, random_arena_chunk_size()); } } diff --git a/test/hotspot/jtreg/compiler/print/CompileCommandPrintMemStat.java b/test/hotspot/jtreg/compiler/print/CompileCommandPrintMemStat.java new file mode 100644 index 00000000000..6ac4b493790 --- /dev/null +++ b/test/hotspot/jtreg/compiler/print/CompileCommandPrintMemStat.java @@ -0,0 +1,102 @@ +/* + * 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. + */ + +/* + * @test + * @summary Checks that -XX:CompileCommand=PrintMemStat,... works + * @library /test/lib + * @run driver compiler.print.CompileCommandPrintMemStat + */ + +package compiler.print; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.util.ArrayList; +import java.util.List; + +public class CompileCommandPrintMemStat { + + final static String METHOD1 = "method1"; + final static String METHOD2 = "method2"; + + public static void main(String[] args) throws Exception { + test(METHOD1, METHOD2); + test(METHOD2, METHOD1); + } + + private static void test(String include, String exclude) throws Exception { + List options = new ArrayList(); + options.add("-Xcomp"); + options.add("-XX:-Inline"); + options.add("-XX:CompileCommand=compileonly," + getTestClass() + "::*"); + options.add("-XX:CompileCommand=MemStat," + getTestMethod(include) + ",print"); + options.add(getTestClass()); + + OutputAnalyzer oa = ProcessTools.executeTestJvm(options); + + // We expect two printouts for "PrintMemStat". A line at compilation time, and a line in a summary report + // that is printed when we exit. Both use the typical ::name format but use / as separator and also + // print the signature. + String expectedNameIncl = getTestMethod(include) + .replace('.', '/') + .replace("$", "\\$"); + String expectedNameExcl = getTestMethod(exclude) + .replace('.', '/') + .replace("$", "\\$"); + + // Should see trace output when methods are compiled + oa.shouldHaveExitValue(0) + .shouldMatch(".*" + expectedNameIncl + ".*") + .shouldNotMatch(".*" + expectedNameExcl + ".*"); + + // Should see final report + // Looks like this: + // total NA RA #nodes time type #rc thread method + // 621832 0 589104 0 0,025 c1 1 0x00007f5ccc1951a0 java/util/zip/ZipFile$Source::checkAndAddEntry((II)I) + oa.shouldMatch("total.*method"); + oa.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameIncl + ".*"); + oa.shouldNotMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameExcl + ".*"); + } + + // Test class that is invoked by the sub process + public static String getTestClass() { + return TestMain.class.getName(); + } + + public static String getTestMethod(String method) { + return getTestClass() + "::" + method; + } + + public static class TestMain { + public static void main(String[] args) { + method1(); + method2(); + } + + static void method1() {} + static void method2() {} + } +} + diff --git a/test/hotspot/jtreg/serviceability/dcmd/compiler/CompilerMemoryStatisticTest.java b/test/hotspot/jtreg/serviceability/dcmd/compiler/CompilerMemoryStatisticTest.java new file mode 100644 index 00000000000..afc79b57142 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/compiler/CompilerMemoryStatisticTest.java @@ -0,0 +1,67 @@ +/* + * 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 jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +import java.util.Iterator; + +/* + * @test CompilerMemoryStatisticTest + * @summary Test Compiler.memory + * @requires vm.compiler1.enabled + * @requires vm.compiler2.enabled + * + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -XX:CompileCommand=memstat,*.* CompilerMemoryStatisticTest + */ + +/* + * @test CompilerMemoryStatisticTest + * @summary Test Compiler.memory + * @requires vm.compiler1.enabled + * @requires vm.compiler2.enabled + * + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @run main/othervm -XX:CompileCommand=memstat,*.*,collect CompilerMemoryStatisticTest + */ + +public class CompilerMemoryStatisticTest { + + public static void main(String args[]) throws Exception { + PidJcmdExecutor executor = new PidJcmdExecutor(); + OutputAnalyzer out = executor.execute("Compiler.memory"); + out.shouldHaveExitValue(0); + + // Looks like this: + // total NA RA #nodes time type #rc thread method + // 621832 0 589104 0 0,025 c1 1 0x00007f5ccc1951a0 java/util/zip/ZipFile$Source.checkAndAddEntry((II)I) + out.shouldMatch("total.*method"); + out.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*java.*\\(.*\\)"); + } +}