diff --git a/src/hotspot/os/aix/vitals_aix.cpp b/src/hotspot/os/aix/vitals_aix.cpp new file mode 100644 index 00000000000..55f4b9470a7 --- /dev/null +++ b/src/hotspot/os/aix/vitals_aix.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + +#include "precompiled.hpp" + +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals_internals.hpp" + +namespace sapmachine_vitals { + +bool platform_columns_initialize() { + return true; +} + +void sample_platform_values(Sample* record) { +} + +} // namespace sapmachine_vitals diff --git a/src/hotspot/os/bsd/vitals_bsd.cpp b/src/hotspot/os/bsd/vitals_bsd.cpp new file mode 100644 index 00000000000..55f4b9470a7 --- /dev/null +++ b/src/hotspot/os/bsd/vitals_bsd.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + +#include "precompiled.hpp" + +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals_internals.hpp" + +namespace sapmachine_vitals { + +bool platform_columns_initialize() { + return true; +} + +void sample_platform_values(Sample* record) { +} + +} // namespace sapmachine_vitals diff --git a/src/hotspot/os/linux/vitals_linux.cpp b/src/hotspot/os/linux/vitals_linux.cpp new file mode 100644 index 00000000000..575d75e3d77 --- /dev/null +++ b/src/hotspot/os/linux/vitals_linux.cpp @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + +#include "precompiled.hpp" +#include "jvm_io.h" +#include "runtime/globals.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" +#include "vitals/vitals_internals.hpp" + +#include +#include +#include + +namespace sapmachine_vitals { + +class ProcFile { + char* _buf; + + // To keep the code simple, I just use a fixed sized buffer. + enum { bufsize = 64*K }; + +public: + + ProcFile() : _buf(NULL) { + _buf = (char*)os::malloc(bufsize, mtInternal); + } + + ~ProcFile () { + os::free(_buf); + } + + bool read(const char* filename) { + + FILE* f = ::fopen(filename, "r"); + if (f == NULL) { + return false; + } + + size_t bytes_read = ::fread(_buf, 1, bufsize, f); + _buf[bufsize - 1] = '\0'; + + ::fclose(f); + + return bytes_read > 0 && bytes_read < bufsize; + } + + const char* text() const { return _buf; } + + const char* get_prefixed_line(const char* prefix) const { + return ::strstr(_buf, prefix); + } + + value_t parsed_prefixed_value(const char* prefix, size_t scale = 1) const { + value_t value = INVALID_VALUE; + const char* const s = get_prefixed_line(prefix); + if (s != NULL) { + errno = 0; + const char* p = s + ::strlen(prefix); + char* endptr = NULL; + value = (value_t)::strtoll(p, &endptr, 10); + if (p == endptr || errno != 0) { + value = INVALID_VALUE; + } else { + value *= scale; + } + } + return value; + } + +}; + +// Returns the sum of RSS and Swap for the process heap segment +static value_t get_process_heap_size() { + FILE* f = ::fopen("/proc/self/smaps", "r"); + value_t result = INVALID_VALUE; + if (f != NULL) { + char line[256]; + int safety = 100000; // for safety reasons; + // Note: this does not guarantee atomicity wrt updates of the underlying file; + // however, we don't care here. We just weed out improbable values. Encountering + // inconsistencies due to concurrent updates should be very rare and probably manifest + // as unparseable lines. + int state = 0; // 1 - found segment, 2 - have rss 3 - have swap + long long rss = 0; + long long swap = 0; + while (state < 3 && ::fgets(line, sizeof(line), f) != NULL && safety > 0) { + // We look for something like this: + // "559f05393000-559f05671000 rw-p 00000000 00:00 0 [heap]" + // ... + // RSS: 100 kB" + // ... + // Swap: 100 kB" + switch (state) { + case 0: + if (::strstr(line, "[heap]") != NULL) { + state = 1; + } + break; + case 1: + if (::sscanf(line, "Rss: %llu kB", &rss) == 1) { + rss *= K; state = 2; + } + break; + case 2: + if (::sscanf(line, "Swap: %llu kB", &swap) == 1) { + swap *= K; state = 3; + } + break; + } + safety --; + } + fclose(f); + + if (state == 3) { + result = swap + rss; + } + } + return result; +} + +struct cpu_values_t { + value_t user; + value_t nice; + value_t system; + value_t idle; + value_t iowait; + value_t steal; + value_t guest; + value_t guest_nice; +}; + +void parse_proc_stat_cpu_line(const char* line, cpu_values_t* out) { + // Note: existence of some of these values depends on kernel version + out->user = out->nice = out->system = out->idle = out->iowait = out->steal = out->guest = out->guest_nice = + INVALID_VALUE; + int user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice; + int num = ::sscanf(line, + "cpu %d %d %d %d %d %d %d %d %d %d", + &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice); + if (num >= 4) { + out->user = user; + out->nice = nice; + out->system = system; + out->idle = idle; + if (num >= 5) { // iowait (5) (since Linux 2.5.41) + out->iowait = iowait; + if (num >= 8) { // steal (8) (since Linux 2.6.11) + out->steal = steal; + if (num >= 9) { // guest (9) (since Linux 2.6.24) + out->guest = guest; + if (num >= 10) { // guest (9) (since Linux 2.6.33) + out->guest_nice = guest_nice; + } + } + } + } + } +} + + +/////// Columns //////// + +// A special class to display cpu time +class CPUTimeColumn: public Column { + + long _clk_tck; + int _num_cores; + + int do_print0(outputStream* st, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const { + // CPU values may overflow, so the delta may be negative. + if (last_value > value) { + return 0; + } + int l = 0; + if (last_value != INVALID_VALUE) { + + // If the last sample is less than one second old, we omit calculating the cpu + // usage. + if (last_value_age > 0) { + + // Values are in ticks. Convert to ms. + const uint64_t value_ms = (value * 1000) / _clk_tck; + const uint64_t last_value_ms = (last_value * 1000) / _clk_tck; + const uint64_t delta_ms = value_ms - last_value_ms; + + // Calculate the number of wallclock milliseconds for the delta interval... + const uint64_t age_ms = last_value_age * 1000; + + // times number of available cores. + const uint64_t total_cpu_time_ms = age_ms * _num_cores; + + // Put the spent cpu time in reference to the total available cpu time. + const double percentage = (100.0f * delta_ms) / total_cpu_time_ms; + + char buf[32]; + l = jio_snprintf(buf, sizeof(buf), "%.0f", percentage); + if (st != NULL) { + st->print_raw(buf); + } + } + } + return l; + } + +public: + CPUTimeColumn(const char* category, const char* header, const char* name, const char* description) + : Column(category, header, name, description, true) + { + _clk_tck = ::sysconf(_SC_CLK_TCK); + _num_cores = os::active_processor_count(); + } + +}; + +//static Column* g_col_system_memtotal = NULL; +static Column* g_col_system_memfree = NULL; +static Column* g_col_system_memavail = NULL; +static Column* g_col_system_memcommitted_ratio = NULL; +static Column* g_col_system_swap = NULL; + +static Column* g_col_system_pages_swapped_in = NULL; +static Column* g_col_system_pages_swapped_out = NULL; + +static Column* g_col_system_num_procs = NULL; +static Column* g_col_system_num_threads = NULL; + +static Column* g_col_system_num_procs_running = NULL; +static Column* g_col_system_num_procs_blocked = NULL; + +static Column* g_col_system_cpu_user = NULL; +static Column* g_col_system_cpu_system = NULL; +static Column* g_col_system_cpu_idle = NULL; +static Column* g_col_system_cpu_waiting = NULL; +static Column* g_col_system_cpu_steal = NULL; +static Column* g_col_system_cpu_guest = NULL; + +static Column* g_col_process_virt = NULL; +static Column* g_col_process_rss = NULL; +static Column* g_col_process_rssanon = NULL; +static Column* g_col_process_rssfile = NULL; +static Column* g_col_process_rssshmem = NULL; +static Column* g_col_process_swapped_out = NULL; +static Column* g_col_process_heap = NULL; + +static Column* g_col_process_cpu_user = NULL; +static Column* g_col_process_cpu_system = NULL; + +static Column* g_col_process_num_of = NULL; +static Column* g_col_process_io_bytes_read = NULL; +static Column* g_col_process_io_bytes_written = NULL; + +static Column* g_col_process_num_threads = NULL; + +bool platform_columns_initialize() { + + // Order matters! +// g_col_system_memtotal = new MemorySizeColumn("system", NULL, "total", "Total physical memory."); + + // Since free and avail are kind of redundant, only display free if avail is not available (very old kernels) + bool have_avail = false; + { + ProcFile bf; + if (bf.read("/proc/meminfo")) { + have_avail = (bf.parsed_prefixed_value("MemAvailable:", 1) != INVALID_VALUE); + } + } + + // To save horizontal space, we print either avail or free + if (have_avail) { // (>=3.14) + g_col_system_memavail = new MemorySizeColumn("system", NULL, "avail", "Memory available without swapping"); + } else { + g_col_system_memfree = new MemorySizeColumn("system", NULL, "free", "Unused memory"); + } + g_col_system_memcommitted_ratio = new PlainValueColumn("system", NULL, "crt", "Committed-to-Commit-Limit ratio (percent)"); + g_col_system_swap = new MemorySizeColumn("system", NULL, "swap", "Swap space used"); + + g_col_system_pages_swapped_in = new DeltaValueColumn("system", NULL, "si", "Number of pages swapped in"); + g_col_system_pages_swapped_out = new DeltaValueColumn("system", NULL, "so", "Number of pages pages swapped out"); + + g_col_system_num_procs = new PlainValueColumn("system", NULL, "p", "Number of processes"); + g_col_system_num_threads = new PlainValueColumn("system", NULL, "t", "Number of threads"); + + g_col_system_num_procs_running = new PlainValueColumn("system", NULL, "pr", "Number of processes running"); + g_col_system_num_procs_blocked = new PlainValueColumn("system", NULL, "pb", "Number of processes blocked"); + + g_col_system_cpu_user = new CPUTimeColumn("system", "cpu", "us", "Global cpu user time"); + g_col_system_cpu_system = new CPUTimeColumn("system", "cpu", "sy", "Global cpu system time"); + g_col_system_cpu_idle = new CPUTimeColumn("system", "cpu", "id", "Global cpu idle time"); + g_col_system_cpu_waiting = new CPUTimeColumn("system", "cpu", "wa", "Global cpu time spent waiting for IO"); + g_col_system_cpu_steal = new CPUTimeColumn("system", "cpu", "st", "Global cpu time stolen"); + g_col_system_cpu_guest = new CPUTimeColumn("system", "cpu", "gu", "Global cpu time spent on guest"); + + g_col_process_virt = new MemorySizeColumn("process", NULL, "virt", "Virtual size"); + + bool have_rss_detail_info = false; + { + ProcFile bf; + if (bf.read("/proc/self/status")) { + have_rss_detail_info = bf.parsed_prefixed_value("RssAnon:", 1) != INVALID_VALUE; + } + } + if (have_rss_detail_info) { + // Linux 4.5 ++ + g_col_process_rss = new MemorySizeColumn("process", "rss", "all", "Resident set size, total"); + g_col_process_rssanon = new MemorySizeColumn("process", "rss", "anon", "Resident set size, anonymous memory (>=4.5)"); + g_col_process_rssfile = new MemorySizeColumn("process", "rss", "file", "Resident set size, file mappings (>=4.5)"); + g_col_process_rssshmem = new MemorySizeColumn("process", "rss", "shm", "Resident set size, shared memory (>=4.5)"); + } else { + g_col_process_rss = new MemorySizeColumn("process", NULL, "rss", "Resident set size, total"); + } + + g_col_process_swapped_out = new MemorySizeColumn("process", NULL, "swdo", "Memory swapped out"); + + // If we manage to locate the heap segment once, and calc its size, we assume it can be done always. + if (get_process_heap_size() != INVALID_VALUE) { + g_col_process_heap = new MemorySizeColumn("process", NULL, "hp", "Process heap segment (brk), resident + swap"); + } + + g_col_process_cpu_user = new CPUTimeColumn("process", "cpu", "us", "Process cpu user time"); + + g_col_process_cpu_system = new CPUTimeColumn("process", "cpu", "sy", "Process cpu system time"); + + g_col_process_num_of = new PlainValueColumn("process", "io", "of", "Number of open files"); + + g_col_process_io_bytes_read = new DeltaMemorySizeColumn("process", "io", "rd", "IO bytes read from storage or cache"); + + g_col_process_io_bytes_written = new DeltaMemorySizeColumn("process", "io", "wr", "IO bytes written"); + + g_col_process_num_threads = new PlainValueColumn("process", NULL, "thr", "Number of native threads"); + + + return true; +} + +static void set_value_in_sample(Column* col, Sample* sample, value_t val) { + if (col != NULL) { + int index = col->index(); + sample->set_value(index, val); + } +} + +// Helper function, returns true if string is a numerical id +static bool is_numerical_id(const char* s) { + const char* p = s; + while(*p >= '0' && *p <= '9') { + p ++; + } + return *p == '\0' ? true : false; +} + +void sample_platform_values(Sample* sample) { + + int idx = 0; + value_t v = 0; + + ProcFile bf; + if (bf.read("/proc/meminfo")) { + + // All values in /proc/meminfo are in KB + const size_t scale = K; + + set_value_in_sample(g_col_system_memfree, sample, + bf.parsed_prefixed_value("MemFree:", scale)); + + set_value_in_sample(g_col_system_memavail, sample, + bf.parsed_prefixed_value("MemAvailable:", scale)); + + value_t swap_total = bf.parsed_prefixed_value("SwapTotal:", scale); + value_t swap_free = bf.parsed_prefixed_value("SwapFree:", scale); + if (swap_total != INVALID_VALUE && swap_free != INVALID_VALUE) { + set_value_in_sample(g_col_system_swap, sample, swap_total - swap_free); + } + + // Calc committed ratio. Values > 100% indicate overcommitment. + value_t commitlimit = bf.parsed_prefixed_value("CommitLimit:", scale); + value_t committed = bf.parsed_prefixed_value("Committed_AS:", scale); + if (commitlimit != INVALID_VALUE && commitlimit != 0 && committed != INVALID_VALUE) { + value_t ratio = (committed * 100) / commitlimit; + set_value_in_sample(g_col_system_memcommitted_ratio, sample, ratio); + } + } + + if (bf.read("/proc/vmstat")) { + set_value_in_sample(g_col_system_pages_swapped_in, sample, bf.parsed_prefixed_value("pswpin")); + set_value_in_sample(g_col_system_pages_swapped_out, sample, bf.parsed_prefixed_value("pswpout")); + } + + if (bf.read("/proc/stat")) { + // Read and parse global cpu values + cpu_values_t values; + const char* line = bf.get_prefixed_line("cpu"); + parse_proc_stat_cpu_line(line, &values); + + set_value_in_sample(g_col_system_cpu_user, sample, values.user + values.nice); + set_value_in_sample(g_col_system_cpu_system, sample, values.system); + set_value_in_sample(g_col_system_cpu_idle, sample, values.idle); + set_value_in_sample(g_col_system_cpu_waiting, sample, values.iowait); + set_value_in_sample(g_col_system_cpu_steal, sample, values.steal); + set_value_in_sample(g_col_system_cpu_guest, sample, values.guest + values.guest_nice); + + set_value_in_sample(g_col_system_num_procs_running, sample, + bf.parsed_prefixed_value("procs_running")); + set_value_in_sample(g_col_system_num_procs_blocked, sample, + bf.parsed_prefixed_value("procs_blocked")); + } + + if (bf.read("/proc/self/status")) { + + set_value_in_sample(g_col_process_virt, sample, bf.parsed_prefixed_value("VmSize:", K)); + set_value_in_sample(g_col_process_swapped_out, sample, bf.parsed_prefixed_value("VmSwap:", K)); + set_value_in_sample(g_col_process_rss, sample, bf.parsed_prefixed_value("VmRSS:", K)); + + set_value_in_sample(g_col_process_rssanon, sample, bf.parsed_prefixed_value("RssAnon:", K)); + set_value_in_sample(g_col_process_rssfile, sample, bf.parsed_prefixed_value("RssFile:", K)); + set_value_in_sample(g_col_process_rssshmem, sample, bf.parsed_prefixed_value("RssShmem:", K)); + + set_value_in_sample(g_col_process_num_threads, sample, + bf.parsed_prefixed_value("Threads:")); + + } + + set_value_in_sample(g_col_process_heap, sample, get_process_heap_size()); + + // Number of open files: iterate over /proc/self/fd and count. + { + DIR* d = ::opendir("/proc/self/fd"); + if (d != NULL) { + value_t v = 0; + struct dirent* en = NULL; + do { + en = ::readdir(d); + if (en != NULL) { + if (::strcmp(".", en->d_name) == 0 || ::strcmp("..", en->d_name) == 0 || + ::strcmp("0", en->d_name) == 0 || ::strcmp("1", en->d_name) == 0 || ::strcmp("2", en->d_name) == 0) { + // omit + } else { + v ++; + } + } + } while(en != NULL); + ::closedir(d); + set_value_in_sample(g_col_process_num_of, sample, v); + } + } + + // Number of processes: iterate over /proc/ and count. + // Number of threads: read "num_threads" from /proc//stat + { + DIR* d = ::opendir("/proc"); + if (d != NULL) { + value_t v_p = 0; + value_t v_t = 0; + struct dirent* en = NULL; + do { + en = ::readdir(d); + if (en != NULL) { + if (is_numerical_id(en->d_name)) { + v_p ++; + char tmp[128]; + jio_snprintf(tmp, sizeof(tmp), "/proc/%s/stat", en->d_name); + if (bf.read(tmp)) { + const char* text = bf.text(); + // See man proc(5) + // (20) num_threads %ld + long num_threads = 0; + ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %ld", &num_threads); + v_t += num_threads; + } + } + } + } while(en != NULL); + ::closedir(d); + set_value_in_sample(g_col_system_num_procs, sample, v_p); + set_value_in_sample(g_col_system_num_threads, sample, v_t); + } + } + + if (bf.read("/proc/self/io")) { + set_value_in_sample(g_col_process_io_bytes_read, sample, + bf.parsed_prefixed_value("rchar:")); + set_value_in_sample(g_col_process_io_bytes_written, sample, + bf.parsed_prefixed_value("wchar:")); + } + + if (bf.read("/proc/self/stat")) { + const char* text = bf.text(); + // See man proc(5) + // (14) utime %lu + // (15) stime %lu + long unsigned cpu_utime = 0; + long unsigned cpu_stime = 0; + ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &cpu_utime, &cpu_stime); + set_value_in_sample(g_col_process_cpu_user, sample, cpu_utime); + set_value_in_sample(g_col_process_cpu_system, sample, cpu_stime); + } + +} + +} // namespace sapmachine_vitals diff --git a/src/hotspot/os/solaris/vitals_solaris.cpp b/src/hotspot/os/solaris/vitals_solaris.cpp new file mode 100644 index 00000000000..190ba1d00ea --- /dev/null +++ b/src/hotspot/os/solaris/vitals_solaris.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + + +#include "precompiled.hpp" + +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals_internals.hpp" + + +namespace sapmachine_vitals { + +bool platform_columns_initialize() { + return true; +} + +void sample_platform_values(Sample* record) { +} + +} // namespace sapmachine_vitals diff --git a/src/hotspot/os/windows/vitals_windows.cpp b/src/hotspot/os/windows/vitals_windows.cpp new file mode 100644 index 00000000000..9022383228b --- /dev/null +++ b/src/hotspot/os/windows/vitals_windows.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + +#include "precompiled.hpp" + +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals_internals.hpp" + +#include + +namespace sapmachine_vitals { + +static Column* g_col_system_memoryload = NULL; +static Column* g_col_system_avail_phys = NULL; +static Column* g_col_process_working_set_size = NULL; +static Column* g_col_process_commit_charge = NULL; + +bool platform_columns_initialize() { + g_col_system_memoryload = new PlainValueColumn("system", NULL, "mload", + "Approximate percentage of physical memory that is in use."); + + // MEMORYSTATUSEX ullAvailPhys + g_col_system_avail_phys = new MemorySizeColumn("system", NULL, "avail-phys", + "Amount of physical memory currently available."); + + // PROCESS_MEMORY_COUNTERS_EX WorkingSetSize + g_col_process_working_set_size = new MemorySizeColumn("process", NULL, "wset", + "Working set size"); + + // PROCESS_MEMORY_COUNTERS_EX PrivateUsage + g_col_process_commit_charge = new MemorySizeColumn("process", NULL, "comch", + "Commit charge"); + + + return true; +} + +static void set_value_in_sample(Column* col, Sample* sample, value_t val) { + if (col != NULL) { + int index = col->index(); + sample->set_value(index, val); + } +} + +void sample_platform_values(Sample* sample) { + + MEMORYSTATUSEX mse; + mse.dwLength = sizeof(mse); + if (::GlobalMemoryStatusEx(&mse)) { + set_value_in_sample(g_col_system_memoryload, sample, mse.dwMemoryLoad); + set_value_in_sample(g_col_system_avail_phys, sample, mse.ullAvailPhys); + } + + PROCESS_MEMORY_COUNTERS cnt; + cnt.cb = sizeof(cnt); + if (::GetProcessMemoryInfo(::GetCurrentProcess(), &cnt, sizeof(cnt))) { + set_value_in_sample(g_col_process_working_set_size, sample, cnt.WorkingSetSize); + set_value_in_sample(g_col_process_commit_charge, sample, cnt.PagefileUsage); + } + +} + +} // namespace sapmachine_vitals diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp index 9e93c4ef926..500a5d99a3e 100644 --- a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp +++ b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp @@ -29,7 +29,11 @@ #include "classfile/javaClasses.hpp" #include "oops/oop.inline.hpp" +// SapMachine 2019-09-01: vitals. +#include "runtime/globals.hpp" #include "runtime/atomic.hpp" +// SapMachine 2019-09-01: vitals. +#include "vitals/vitals.hpp" inline ClassLoaderData *ClassLoaderDataGraph::find_or_create(Handle loader) { guarantee(loader() != NULL && oopDesc::is_oop(loader()), "Loader must be oop"); @@ -52,20 +56,36 @@ size_t ClassLoaderDataGraph::num_array_classes() { void ClassLoaderDataGraph::inc_instance_classes(size_t count) { Atomic::add(&_num_instance_classes, count); + // SapMachine 2019-02-20 : vitals + if (EnableVitals) { + sapmachine_vitals::counters::inc_classes_loaded(count); + } } void ClassLoaderDataGraph::dec_instance_classes(size_t count) { assert(count <= _num_instance_classes, "Sanity"); Atomic::sub(&_num_instance_classes, count); + // SapMachine 2019-02-20 : vitals + if (EnableVitals) { + sapmachine_vitals::counters::inc_classes_unloaded(count); + } } void ClassLoaderDataGraph::inc_array_classes(size_t count) { Atomic::add(&_num_array_classes, count); + // SapMachine 2019-02-20 : vitals + if (EnableVitals) { + sapmachine_vitals::counters::inc_classes_loaded(count); + } } void ClassLoaderDataGraph::dec_array_classes(size_t count) { assert(count <= _num_array_classes, "Sanity"); Atomic::sub(&_num_array_classes, count); + // SapMachine 2019-02-20 : vitals + if (EnableVitals) { + sapmachine_vitals::counters::inc_classes_unloaded(count); + } } bool ClassLoaderDataGraph::should_clean_metaspaces_and_reset() { diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index d98d0ad5226..41e0c9f80c2 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -497,6 +497,29 @@ const intx ObjectAlignmentInBytes = 8; develop(bool, Verbose, false, \ "Print additional debugging information from other modes") \ \ + /* SapMachine 2019-02-20 : vitals */ \ + product(bool, EnableVitals, true, \ + "Enable sampling of vitals: memory, cpu utilization and various " \ + "VM core statistics; display via jcmd \"VM.vitals\".") \ + \ + product(uintx, VitalsSampleInterval, 10, \ + "Vitals sample rate interval in seconds (default 10)") \ + \ + product(bool, VitalsLockFreeSampling, false, DIAGNOSTIC, \ + "When sampling vitals, omit any actions which require locking.") \ + \ + product(bool, DumpVitalsAtExit, false, \ + "Dump vitals at VM exit into two files, by default called " \ + "sapmachine_vitals_.txt and sapmachine_vitals_.csv. " \ + "Use -XX:VitalsFile option to change the file names.") \ + \ + product(bool, PrintVitalsAtExit, false, \ + "Prints vitals at VM exit to tty.") \ + \ + product(ccstr, VitalsFile, NULL, \ + "When DumpVitalsAtExit is set, the file name prefix for the " \ + "output files (default is sapmachine_vitals_).") \ + \ develop(bool, PrintMiscellaneous, false, \ "Print uncategorized debugging information (requires +Verbose)") \ \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index f1c591659a5..6dbd612d9c8 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -59,6 +59,8 @@ #include "prims/jvmtiExport.hpp" #include "runtime/deoptimization.hpp" #include "runtime/flags/flagSetting.hpp" +// SapMachine 2019-09-01: vitals. +#include "runtime/globals.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -78,6 +80,8 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +// SapMachine 2019-09-01: vitals. +#include "vitals/vitals.hpp" #ifdef COMPILER1 #include "c1/c1_Compiler.hpp" #include "c1/c1_Runtime1.hpp" @@ -341,6 +345,14 @@ void print_statistics() { MetaspaceUtils::print_basic_report(tty, 0); } + // SapMachine 2019-09-01: vitals. + if (DumpVitalsAtExit) { + sapmachine_vitals::dump_reports(); + } + if (PrintVitalsAtExit) { + sapmachine_vitals::print_report(tty); + } + ThreadsSMRSupport::log_statistics(); } @@ -384,6 +396,14 @@ void print_statistics() { MetaspaceUtils::print_basic_report(tty, 0); } + // SapMachine 2019-09-01: vitals. + if (DumpVitalsAtExit) { + sapmachine_vitals::dump_reports(); + } + if (PrintVitalsAtExit) { + sapmachine_vitals::print_report(tty); + } + if (LogTouchedMethods && PrintTouchedMethodsAtExit) { Method::print_touched_methods(tty); } diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index a2a41754ede..187e1efe121 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -129,6 +129,8 @@ #include "utilities/preserveException.hpp" #include "utilities/spinYield.hpp" #include "utilities/vmError.hpp" +// SapMachine 2019-02-20 : vitals +#include "vitals/vitals.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmci.hpp" #include "jvmci/jvmciEnv.hpp" @@ -2998,6 +3000,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { JFR_ONLY(Jfr::on_create_vm_3();) + // SapMachine 2019-02-20 : vitals + if (EnableVitals) { + sapmachine_vitals::initialize(); + } + #if INCLUDE_MANAGEMENT Management::initialize(THREAD); @@ -3456,6 +3463,7 @@ void Threads::add(JavaThread* p, bool force_daemon) { p->set_on_thread_list(); _number_of_threads++; + oop threadObj = p->threadObj(); bool daemon = true; // Bootstrapping problem: threadObj can be null for initial @@ -3478,6 +3486,10 @@ void Threads::add(JavaThread* p, bool force_daemon) { // Make new thread known to active EscapeBarrier EscapeBarrier::thread_added(p); + + // SapMachine 2019-02-20 : vitals + sapmachine_vitals::counters::inc_threads_created(1); + } void Threads::remove(JavaThread* p, bool is_daemon) { @@ -3733,6 +3745,13 @@ void Threads::print_on(outputStream* st, bool print_stacks, cl.do_thread(WatcherThread::watcher_thread()); cl.do_thread(AsyncLogWriter::instance()); + // SapMachine 2019-11-07 : vitals + const Thread* vitals_sampler_thread = sapmachine_vitals::samplerthread(); + if (vitals_sampler_thread != NULL) { + vitals_sampler_thread->print_on(st); + st->cr(); + } + st->flush(); } @@ -3787,6 +3806,10 @@ void Threads::print_on_error(outputStream* st, Thread* current, char* buf, print_on_error(WatcherThread::watcher_thread(), st, current, buf, buflen, &found_current); print_on_error(AsyncLogWriter::instance(), st, current, buf, buflen, &found_current); + // SapMachine 2019-11-07 : vitals + print_on_error(const_cast(sapmachine_vitals::samplerthread()), + st, current, buf, buflen, &found_current); + if (Universe::heap() != NULL) { PrintOnErrorClosure print_closure(st, current, buf, buflen, &found_current); Universe::heap()->gc_threads_do(&print_closure); diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 1b3710cf004..21415932d85 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -62,6 +62,10 @@ #include "trimCHeapDCmd.hpp" #endif +// SapMachine 2019-02-20 : vitals +#include "vitals/vitalsDCmd.hpp" + + static void loadAgentModule(TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); @@ -147,6 +151,10 @@ void DCmdRegistrant::register_dcmds(){ #if INCLUDE_CDS DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // INCLUDE_CDS + + // SapMachine 2019-02-20 : vitals + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + } #ifndef HAVE_EXTRA_DCMD diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 29755fec2da..6e36be60f29 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -23,6 +23,7 @@ * */ + #include "precompiled.hpp" #include "jvm.h" #include "cds/metaspaceShared.hpp" @@ -60,6 +61,8 @@ #include "utilities/events.hpp" #include "utilities/vmError.hpp" #include "utilities/macros.hpp" +// SapMachine 2019-02-20 : vitals +#include "vitals/vitals.hpp" #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif @@ -1130,6 +1133,15 @@ void VMError::report(outputStream* st, bool _verbose) { MemTracker::error_report(st); } + // SapMachine 2019-02-20 : vitals + STEP("Vitals") + if (_verbose) { + sapmachine_vitals::print_info_t info; + sapmachine_vitals::default_settings(&info); + info.sample_now = true; // About the only place where we do this apart from explicitly setting the "now" parm on jcmd + sapmachine_vitals::print_report(st); + } + STEP("printing system") if (_verbose) { @@ -1308,6 +1320,13 @@ void VMError::print_vm_info(outputStream* st) { MemTracker::error_report(st); + // SapMachine 2019-02-20 : vitals + // STEP("Vitals") + sapmachine_vitals::print_info_t info; + sapmachine_vitals::default_settings(&info); + info.sample_now = false; + sapmachine_vitals::print_report(st); + // STEP("printing system") st->cr(); diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp new file mode 100644 index 00000000000..4d044f8d4f0 --- /dev/null +++ b/src/hotspot/share/vitals/vitals.cpp @@ -0,0 +1,1331 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + +#include "precompiled.hpp" +#include "jvm_io.h" + +#include "gc/shared/collectedHeap.hpp" +#include "classfile/classLoaderDataGraph.inline.hpp" +#include "code/codeCache.hpp" +#include "memory/allocation.hpp" +#include "memory/metaspace.hpp" +#include "memory/metaspaceUtils.hpp" +#include "memory/universe.hpp" +#include "runtime/os.hpp" +#include "runtime/mutex.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/nonJavaThread.hpp" +#include "runtime/thread.hpp" +#include "services/memTracker.hpp" +#include "services/mallocTracker.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" +#include "utilities/ostream.hpp" +#include "vitals/vitals.hpp" +#include "vitals/vitals_internals.hpp" + +#include +#include + + +static Mutex* g_vitals_lock = NULL; + +namespace sapmachine_vitals { + +namespace counters { + +static volatile size_t g_classes_loaded = 0; +static volatile size_t g_classes_unloaded = 0; +static volatile size_t g_threads_created = 0; + +void inc_classes_loaded(size_t count) { + Atomic::add(&g_classes_loaded, count); +} + +void inc_classes_unloaded(size_t count) { + Atomic::add(&g_classes_unloaded, count); +} + +void inc_threads_created(size_t count) { + Atomic::add(&g_threads_created, count); +} + +} // namespace counters + +// helper function for the missing outputStream::put(int c, int repeat) +static void ostream_put_n(outputStream* st, int c, int repeat) { + for (int i = 0; i < repeat; i ++) { + st->put(c); + } +} + +/////// class Sample ///// + +int Sample::num_values() { return ColumnList::the_list()->num_columns(); } + +size_t Sample::size_in_bytes() { + assert(num_values() > 0, "not yet initialized"); + return sizeof(Sample) + sizeof(value_t) * (num_values() - 1); // -1 since Sample::values is size 1 to shut up compilers about zero length arrays +} + +Sample* Sample::allocate() { + Sample* s = (Sample*) NEW_C_HEAP_ARRAY(char, size_in_bytes(), mtInternal); + s->reset(); + return s; +} + +void Sample::reset() { + for (int i = 0; i < num_values(); i ++) { + set_value(i, INVALID_VALUE); + } + DEBUG_ONLY(_num = -1;) + _timestamp = 0; +} + +void Sample::set_value(int index, value_t v) { + assert(index >= 0 && index < num_values(), "invalid index"); + _values[index] = v; +} + +void Sample::set_timestamp(time_t t) { + _timestamp = t; +} + +#ifdef ASSERT +void Sample::set_num(int n) { + _num = n; +} +#endif + +value_t Sample::value(int index) const { + assert(index >= 0 && index < num_values(), "invalid index"); + return _values[index]; +} + +static void print_text_with_dashes(outputStream* st, const char* text, int width) { + assert(width > 0, "Sanity"); + // Print the name centered within the width like this + // ----- system ------ + int extra_space = width - (int)strlen(text); + if (extra_space > 0) { + int left_space = extra_space / 2; + int right_space = extra_space - left_space; + ostream_put_n(st, '-', left_space); + st->print_raw(text); + ostream_put_n(st, '-', right_space); + } else { + ostream_put_n(st, '-', width); + } +} + +// Helper function for printing: +// Print to ostream, but only if ostream is given. In any case return number of +// characters printed (or which would have been printed). +static +ATTRIBUTE_PRINTF(2, 3) +int printf_helper(outputStream* st, const char *fmt, ...) { + // We only print numbers, so a small buffer is fine. + char buf[128]; + va_list args; + int len = 0; + va_start(args, fmt); + len = jio_vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + // jio_vsnprintf guarantees -1 on truncation, and always zero termination if buffersize > 0. + assert(len >= 0, "Error, possible truncation. Increase bufsize?"); + if (len < 0) { // Handle in release too: just print a clear marker + jio_snprintf(buf, sizeof(buf), "!ERR!"); + len = (int)::strlen(buf); + } + if (st != NULL) { + st->print_raw(buf); + } + return len; +} + +// length of time stamp +#define TIMESTAMP_LEN 19 +// number of spaces after time stamp +#define TIMESTAMP_DIVIDER_LEN 3 +static void print_timestamp(outputStream* st, time_t t) { + struct tm _tm; + if (os::localtime_pd(&t, &_tm) == &_tm) { + char buf[32]; + ::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &_tm); + st->print("%*s", TIMESTAMP_LEN, buf); + } +} + + +////// ColumnWidths : a helper class for pre-calculating column widths to make a table align nicely. +// Keeps an array of ints, dynamically sized (since each platform has a different number of columns), +// and offers methods of auto-sizeing them to fit given samples (via dry-printing). +class ColumnWidths { + int* _widths; +public: + + ColumnWidths() { + // Allocate array; initialize with the minimum required column widths (which is the + // size required to print the column header fully) + _widths = NEW_C_HEAP_ARRAY(int, ColumnList::the_list()->num_columns(), mtInternal); + const Column* c = ColumnList::the_list()->first(); + while (c != NULL) { + _widths[c->index()] = (int)::strlen(c->name()); + c = c->next(); + } + } + + // given a sample (and an optional preceding sample for delta values), + // update widths to accommodate sample values (uses dry-printing) + void update_from_sample(const Sample* sample, const Sample* last_sample, const print_info_t* pi) { + const Column* c = ColumnList::the_list()->first(); + while (c != NULL) { + const int idx = c->index(); + const value_t v = sample->value(idx); + value_t v2 = INVALID_VALUE; + int age = -1; + if (last_sample != NULL) { + v2 = last_sample->value(idx); + age = sample->timestamp() - last_sample->timestamp(); + } + int needed = c->calc_print_size(v, v2, age, pi); + if (_widths[idx] < needed) { + _widths[idx] = needed; + } + c = c->next(); + } + } + + int at(int index) const { + return _widths[index]; + } +}; + +////// ColumnList: a singleton class holding all information about all columns + +ColumnList* ColumnList::_the_list = NULL; + +bool ColumnList::initialize() { + _the_list = new ColumnList(); + return _the_list != NULL; +} + +void ColumnList::add_column(Column* c) { + assert(c->index() == -1, "Do not add twice."); + Column* c_last = _last; + if (_last != NULL) { + _last->_next = c; + _last = c; + } else { + _first = _last = c; + } + // fix indices (describe position of column within table/category/header + c->_idx = c->_idx_cat = c->_idx_hdr = 0; + if (c_last != NULL) { + c->_idx = c_last->_idx + 1; + if (::strcmp(c->category(), c_last->category()) == 0) { // same category as last column? + c->_idx_cat = c_last->_idx_cat + 1; + } + if (c->header() != NULL && c_last->header() != NULL && + ::strcmp(c_last->header(), c->header()) == 0) { // have header and same as last column? + c->_idx_hdr = c_last->_idx_hdr + 1; + } + } + _num_columns ++; +} + +//////////////////// + +static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { + + assert(pi->csv == false, "Not in csv mode"); + ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN); + + const Column* c = ColumnList::the_list()->first(); + assert(c != NULL, "no columns?"); + const char* last_category_text = NULL; + int width = 0; + + while(c != NULL) { + if (c->index_within_category_section() == 0) { + if (width > 0) { + // Print category label centered over the last n columns, surrounded by dashes. + print_text_with_dashes(st, last_category_text, width - 1); + st->put(' '); + } + width = 0; + } + width += widths->at(c->index()); + width += 1; // divider between columns + last_category_text = c->category(); + c = c->next(); + } + print_text_with_dashes(st, last_category_text, width - 1); + st->cr(); +} + +static void print_header_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { + + assert(pi->csv == false, "Not in csv mode"); + ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN); + + const Column* c = ColumnList::the_list()->first(); + assert(c != NULL, "no columns?"); + const char* last_header_text = NULL; + int width = 0; + + while(c != NULL) { + if (c->index_within_header_section() == 0) { // First in header section + if (width > 0) { + if (last_header_text != NULL) { + // Print header label centered over the last n columns, surrounded by dashes. + print_text_with_dashes(st, last_header_text, width - 1); + st->put(' '); // divider + } else { + // the last n columns had no header. Just fill with blanks. + ostream_put_n(st, ' ', width); + } + } + width = 0; + } + width += widths->at(c->index()); + width += 1; // divider between columns + last_header_text = c->header(); + c = c->next(); + } + if (width > 0 && last_header_text != NULL) { + print_text_with_dashes(st, last_header_text, width - 1); + } + st->cr(); +} + +static void print_column_names(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { + + // Leave space for timestamp column + if (pi->csv == false) { + ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN); + } else { + st->put(','); + } + + const Column* c = ColumnList::the_list()->first(); + const Column* previous = NULL; + while (c != NULL) { + if (pi->csv == false) { + st->print("%-*s ", widths->at(c->index()), c->name()); + } else { // csv mode + // csv: use comma as delimiter, don't pad, and precede name with category/header + // (limited to 4 chars). + if (c->category() != NULL) { + st->print("%.4s-", c->category()); + } + if (c->header() != NULL) { + st->print("%.4s-", c->header()); + } + st->print("%s,", c->name()); + } + previous = c; + c = c->next(); + } + st->cr(); +} + +static void print_legend(outputStream* st, const print_info_t* pi) { + const Column* c = ColumnList::the_list()->first(); + const Column* c_prev = NULL; + while (c != NULL) { + // Print category label. + if (c->index_within_category_section() == 0) { + print_text_with_dashes(st, c->category(), 30); + st->cr(); + } + // print column name and description + const int min_width_column_label = 16; + char buf[32]; + if (c->header() != NULL) { + jio_snprintf(buf, sizeof(buf), "%s-%s", c->header(), c->name()); + } else { + jio_snprintf(buf, sizeof(buf), "%s", c->name()); + } + st->print("%*s: %s", min_width_column_label, buf, c->description()); + + // If column is a delta value, indicate so + if (c->is_delta()) { + st->print_raw(" [delta]"); + } + + st->cr(); + + c_prev = c; + c = c->next(); + } + st->cr(); + st->print_cr("[delta] values refer to the previous measurement."); + if (pi->scale != 0) { + const char* display_unit = NULL; + switch (pi->scale) { + case 1: display_unit = " "; break; + case K: display_unit = "KB"; break; + case M: display_unit = "MB"; break; + case G: display_unit = "GB"; break; + default: ShouldNotReachHere(); + } + st->print_cr("[mem] values are in %s.", display_unit); + } +} + +// Print a human readable size. +// byte_size: size, in bytes, to be printed. +// scale: K,M,G or 0 (dynamic) +// width: printing width. +static int print_memory_size(outputStream* st, size_t byte_size, size_t scale) { + + // If we forced a unit via scale=.. argument, we suppress display of the unit + // since we already know which unit is used. That saves horizontal space and + // makes automatic processing of the data easier. + bool dynamic_mode = false; + + if (scale == 0) { + dynamic_mode = true; + // Dynamic mode. Choose scale for this value. + if (byte_size == 0) { + scale = K; + } else { + if (byte_size >= G) { + scale = G; + } else if (byte_size >= M) { + scale = M; + } else { + scale = K; + } + } + } + + const char* display_unit = ""; + if (dynamic_mode) { + switch(scale) { + case K: display_unit = "k"; break; + case M: display_unit = "m"; break; + case G: display_unit = "g"; break; + default: + ShouldNotReachHere(); + } + } + + // How we display stuff: + // scale=1 (manually set) - print exact byte values without unit + // scale=0 (default, dynamic mode) - print values < 1024KB as "..k", <1024MB as "..m", "..g" above that + // - to distinguish between 0 and "almost 0" print very small values as "<1K" + // - print "k", "m" values with precision 0, "g" values with precision 1. + // scale=k,m or g (manually set) - print value divided by scale and without unit. No smart printing. + // Used mostly for automated processing, lets keep parsing simple. + + int l = 0; + if (scale == 1) { + // scale = 1 - print exact bytes + l = printf_helper(st, SIZE_FORMAT, byte_size); + + } else { + const float display_value = (float) byte_size / scale; + if (dynamic_mode) { + // dynamic scale + const int precision = scale >= G ? 1 : 0; + if (byte_size > 0 && byte_size < 1 * K) { + // very small but not zero. + assert(scale == K, "Sanity"); + l = printf_helper(st, "<1%s", display_unit); + } else { + l = printf_helper(st, "%.*f%s", precision, display_value, display_unit); + } + } else { + // fixed scale K, M or G + const int precision = 0; + l = printf_helper(st, "%.*f%s", precision, display_value, display_unit); + } + } + + return l; + +} + +///////// class Column and childs /////////// + +Column::Column(const char* category, const char* header, const char* name, const char* description, bool delta) + : _category(category), + _header(header), // may be NULL + _name(name), + _description(description), + _delta(delta), + _next(NULL), _idx(-1), + _idx_cat(-1), _idx_hdr(-1) +{ + ColumnList::the_list()->add_column(this); +} + +void Column::print_value(outputStream* st, value_t value, value_t last_value, + int last_value_age, int min_width, const print_info_t* pi) const { + + // We print all values right aligned. + int needed = calc_print_size(value, last_value, last_value_age, pi); + if (pi->csv == false && min_width > needed) { + // In ascii (non csv) mode, pad to minimum width + ostream_put_n(st, ' ', min_width - needed); + } + // csv values shall be enclosed in quotes. + if (pi->csv) { + st->put('"'); + } + do_print(st, value, last_value, last_value_age, pi); + if (pi->csv) { + st->put('"'); + } +} + +// Returns the number of characters this value needs to be printed. +int Column::calc_print_size(value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const { + return do_print(NULL, value, last_value, last_value_age, pi); +} + +int Column::do_print(outputStream* st, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const { + if (value == INVALID_VALUE) { + if (pi->raw) { + if (st != NULL) { + return printf_helper(st, "%s", "?"); + } + return 1; + } else { + return 0; + } + } else { + if (pi->raw) { + return printf_helper(st, UINT64_FORMAT, value); + } else { + return do_print0(st, value, last_value, last_value_age, pi); + } + } +} + +int PlainValueColumn::do_print0(outputStream* st, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const +{ + return printf_helper(st, UINT64_FORMAT, value); +} + +int DeltaValueColumn::do_print0(outputStream* st, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const { + if (_show_only_positive && last_value > value) { + // we assume the underlying value to be monotonically raising, and that + // any negative delta would be just a fluke (e.g. counter overflows) + // we do not want to show + return 0; + } + if (last_value != INVALID_VALUE) { + return printf_helper(st, INT64_FORMAT, (int64_t)(value - last_value)); + } + return 0; +} + +int MemorySizeColumn::do_print0(outputStream* st, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const { + return print_memory_size(st, value, pi->scale); +} + +int DeltaMemorySizeColumn::do_print0(outputStream* st, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const { + if (last_value != INVALID_VALUE) { + return print_memory_size(st, value - last_value, pi->scale); + } + return 0; +} + +////////////// sample printing /////////////////////////// + +// Print one sample. +static void print_one_sample(outputStream* st, const Sample* sample, + const Sample* last_sample, const ColumnWidths* widths, const print_info_t* pi) { + + // Print timestamp and divider + if (sample->timestamp() == 0) { + st->print("%*s", TIMESTAMP_LEN, "Now"); + } else { + print_timestamp(st, sample->timestamp()); + } + + // For analysis, print sample numbers +#ifdef ASSERT + if (pi->raw) { + st->print(",%d,%d", sample->num(), + last_sample != NULL ? last_sample->num() : -1); + } +#endif + + if (pi->csv == false) { + ostream_put_n(st, ' ', TIMESTAMP_DIVIDER_LEN); + } else { + st->put(','); + } + + const Column* c = ColumnList::the_list()->first(); + while (c != NULL) { + const int idx = c->index(); + const value_t v = sample->value(idx); + value_t v2 = INVALID_VALUE; + int age = -1; + if (last_sample != NULL) { + v2 = last_sample->value(idx); + age = sample->timestamp() - last_sample->timestamp(); + } + const int min_width = widths->at(idx); + c->print_value(st, v, v2, age, min_width, pi); + st->put(pi->csv ? ',' : ' '); + c = c->next(); + } + st->cr(); +} + +////////////// Class SampleTable ///////////////////////// + +// A fixed sized fifo buffer of n samples +class SampleTable : public CHeapObj { + + const int _num_entries; + int _head; // Index of the last sample written; -1 if none have been written yet + bool _did_wrap; + Sample* _samples; + +#ifdef ASSERT + void verify() const { + assert(_samples != NULL, "sanity"); + assert(_head >= 0 && _head < _num_entries, "sanity"); + } +#endif + + const size_t sample_offset_in_bytes(int idx) const { + assert(idx >= 0 && idx <= _num_entries, "invalid index: %d", idx); + return Sample::size_in_bytes() * idx; + } + const Sample* sample_at(int index) const { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); } + Sample* sample_at(int index) { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); } + +public: + + SampleTable(int num_entries) + : _num_entries(num_entries), + _head(-1), + _did_wrap(false), + _samples(NULL) + { + _samples = (Sample*) NEW_C_HEAP_ARRAY(char, Sample::size_in_bytes() * _num_entries, mtInternal); +#ifdef ASSERT + for (int i = 0; i < _num_entries; i ++) { + sample_at(i)->reset(); + } +#endif + } + + bool is_empty() const { return _head == -1; } + + void add_sample(const Sample* sample) { + assert_lock_strong(g_vitals_lock); + // Advance head + _head ++; + if (_head == _num_entries) { + _did_wrap = true; + _head = 0; + } + // Copy sample + ::memcpy(sample_at(_head), sample, Sample::size_in_bytes()); + DEBUG_ONLY(verify()); + } + + // Given a valid sample index, return the previous index or -1 if this is the oldest sample. + int get_previous_index(int idx) const { + assert(idx >= 0 && idx <= _num_entries, "index oob: %d", idx); + assert(_did_wrap == true || idx <= _head, "index invalid: %d", idx); + int prev = idx - 1; + if (prev == -1 && _did_wrap) { + prev = _num_entries - 1; + } + if (prev == _head) { + prev = -1; + } + return prev; + } + + class Closure { + public: + virtual void do_sample(const Sample* sample, const Sample* previous_sample) = 0; + }; + + void call_closure_for_sample_at(Closure* closure, int idx) const { + const Sample* sample = sample_at(idx); + int idx2 = get_previous_index(idx); + const Sample* previous_sample = idx2 == -1 ? NULL : sample_at(idx2); + closure->do_sample(sample, previous_sample); + } + + void walk_table_locked(Closure* closure, bool youngest_to_oldest = true) const { + assert_lock_strong(g_vitals_lock); + + if (_head == -1) { + return; + } + + DEBUG_ONLY(verify();) + + if (youngest_to_oldest) { // youngest to oldest + for (int pos = _head; pos >= 0; pos--) { + call_closure_for_sample_at(closure, pos); + } + if (_did_wrap) { + for (int pos = _num_entries - 1; pos > _head; pos--) { + call_closure_for_sample_at(closure, pos); + } + } + } else { // oldest to youngest + if (_did_wrap) { + for (int pos = _head + 1; pos < _num_entries; pos ++) { + call_closure_for_sample_at(closure, pos); + } + } + for (int pos = 0; pos <= _head; pos ++) { + call_closure_for_sample_at(closure, pos); + } + } + } + +}; + +class MeasureColumnWidthsClosure : public SampleTable::Closure { + const print_info_t* const _pi; + ColumnWidths* const _widths; + +public: + MeasureColumnWidthsClosure(const print_info_t* pi, ColumnWidths* widths) : + _pi(pi), _widths(widths) {} + + void do_sample(const Sample* sample, const Sample* previous_sample) { + _widths->update_from_sample(sample, previous_sample, _pi); + } +}; + +class PrintSamplesClosure : public SampleTable::Closure { + outputStream* const _st; + const print_info_t* const _pi; + const ColumnWidths* const _widths; + +public: + + PrintSamplesClosure(outputStream* st, const print_info_t* pi, const ColumnWidths* widths) : + _st(st), _pi(pi), _widths(widths) {} + + void do_sample(const Sample* sample, const Sample* previous_sample) { + print_one_sample(_st, sample, previous_sample, _widths, _pi); + } +}; + +// sampleTables is a combination of three tables: a short term table, a mid term table, a long term table. +// It takes care to feed new samples into these tables at the appropriate intervals. +class SampleTables: public CHeapObj { + + // Note that sample intervals are bound to VitalsSampleInterval switch. Changing that changes the + // clock for all tables. + + // short term: 10 seconds per sample, 360 samples or 60 minutes total + static const int short_term_interval_default = 10; + static const int short_term_num_samples = 360; + + SampleTable _short_term_table; + + // Downsample tables: + + // mid term: 10 minutes per sample (600 seconds or 60 short term samples), 144 samples or 24 hours in total + static const int mid_term_interval_ratio = 60; + static const int mid_term_num_samples = 144; + + // long term history: 2 hour intervals (7200 seconds or 720 short term samples), 120 samples or 10 days in total + static const int long_term_interval_ratio = 720; + static const int long_term_num_samples = 120; + + SampleTable _mid_term_table; + SampleTable _long_term_table; + + int _count; + + static void print_table(const SampleTable* table, outputStream* st, + const ColumnWidths* widths, const print_info_t* pi) { + if (table->is_empty()) { + st->print_cr("(no samples)"); + return; + } + PrintSamplesClosure prclos(st, pi, widths); + table->walk_table_locked(&prclos, !pi->reverse_ordering); + } + + static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { + if (pi->csv == false) { + print_category_line(st, widths, pi); + print_header_line(st, widths, pi); + } + print_column_names(st, widths, pi); + st->cr(); + } + + // Helper, print a time span given in seconds- + static void print_time_span(outputStream* st, int secs) { + const int mins = secs / 60; + const int hrs = secs / (60 * 60); + const int days = secs / (60 * 60 * 24); + if (days > 1) { + st->print_cr("Last %d days:", days); + } else if (hrs > 1) { + st->print_cr("Last %d hours:", hrs); + } else if (mins > 1) { + st->print_cr("Last %d minutes:", mins); + } else { + st->print_cr("Last %d seconds:", secs); + } + } + +public: + + SampleTables() + : _short_term_table(short_term_num_samples), + _mid_term_table(mid_term_num_samples), + _long_term_table(long_term_num_samples), + _count(0) + {} + + void add_sample(const Sample* sample) { + MutexLocker ml(g_vitals_lock, Mutex::_no_safepoint_check_flag); + _short_term_table.add_sample(sample); + // Feed downsample tables too, but increment first, so the down-sample tables + // are only fed after an initial sample interval has passed. This prevents + // filling them up immediately which can be confusing to readers. + _count++; + if ((_count % mid_term_interval_ratio) == 0) { + _mid_term_table.add_sample(sample); + } + if ((_count % long_term_interval_ratio) == 0) { + _long_term_table.add_sample(sample); + } + } + + void print_all(outputStream* st, const print_info_t* pi, const Sample* sample_now) const { + + MutexLocker ml(g_vitals_lock, Mutex::_no_safepoint_check_flag); + + // Pre-calc column widths needed to display all tables and values nicely aligned + ColumnWidths widths; + + MeasureColumnWidthsClosure mcwclos(pi, &widths); + _short_term_table.walk_table_locked(&mcwclos); + _mid_term_table.walk_table_locked(&mcwclos); + _long_term_table.walk_table_locked(&mcwclos); + if (sample_now != NULL) { + widths.update_from_sample(sample_now, NULL, pi); + } + + // Now print + if (sample_now != NULL) { + st->print_cr("Now:"); + print_headers(st, &widths, pi); + print_one_sample(st, sample_now, NULL, &widths, pi); + } + st->cr(); + + print_time_span(st, VitalsSampleInterval * short_term_num_samples); + print_headers(st, &widths, pi); + print_table(&_short_term_table, st, &widths, pi); + st->cr(); + + print_time_span(st, VitalsSampleInterval * mid_term_interval_ratio * mid_term_num_samples); + print_headers(st, &widths, pi); + print_table(&_mid_term_table, st, &widths, pi); + st->cr(); + + print_time_span(st, VitalsSampleInterval * long_term_interval_ratio * long_term_num_samples); + print_headers(st, &widths, pi); + print_table(&_long_term_table, st, &widths, pi); + st->cr(); + + st->cr(); + + } + +}; + +static SampleTables* g_all_tables = NULL; + +/////////////// SAMPLING ////////////////////// + +// Samples all values, but leaves timestamp unchanged +static void sample_values(Sample* sample, bool avoid_locking) { + sample_jvm_values(sample, avoid_locking); + sample_platform_values(sample); +} + +class SamplerThread: public NamedThread { + + Sample* _sample; + bool _stop; + int _samples_taken; + int _jump_cooldown; + + static int get_sample_interval_ms() { + return (int)VitalsSampleInterval * 1000; + } + + void take_sample() { + + _sample->reset(); + + time_t t; + ::time(&t); + _sample->set_timestamp(t); + DEBUG_ONLY(_sample->set_num(_samples_taken);) + _samples_taken ++; + sample_values(_sample, VitalsLockFreeSampling); + g_all_tables->add_sample(_sample); + + } + +public: + + SamplerThread() + : NamedThread(), + _sample(NULL), + _stop(false), + _samples_taken(0), + _jump_cooldown(0) + { + _sample = Sample::allocate(); + this->set_name("vitals sampler thread"); + } + + virtual void run() { + record_stack_base_and_size(); + for (;;) { + take_sample(); + os::naked_sleep(get_sample_interval_ms()); + if (_stop) { + break; + } + } + } + + void stop() { + _stop = true; + } + +}; + +static SamplerThread* g_sampler_thread = NULL; + +static bool initialize_sampler_thread() { + g_sampler_thread = new SamplerThread(); + if (g_sampler_thread != NULL) { + if (os::create_thread(g_sampler_thread, os::os_thread)) { + os::start_thread(g_sampler_thread); + } + return true; + } + return false; +} + + +/////////////////////////////////////// +/////// JVM-specific columns ////////// + +static Column* g_col_heap_committed = NULL; +static Column* g_col_heap_used = NULL; + +static Column* g_col_metaspace_committed = NULL; +static Column* g_col_metaspace_used = NULL; +static Column* g_col_classspace_committed = NULL; +static Column* g_col_classspace_used = NULL; +static Column* g_col_metaspace_cap_until_gc = NULL; + +static Column* g_col_codecache_committed = NULL; + +static Column* g_col_nmt_malloc = NULL; + +static Column* g_col_number_of_java_threads = NULL; +static Column* g_col_number_of_java_threads_non_demon = NULL; +static Column* g_col_size_thread_stacks = NULL; +static Column* g_col_number_of_java_threads_created = NULL; + +static Column* g_col_number_of_clds = NULL; +static Column* g_col_number_of_anon_clds = NULL; + +static Column* g_col_number_of_classes = NULL; +static Column* g_col_number_of_class_loads = NULL; +static Column* g_col_number_of_class_unloads = NULL; + +//... + +static bool add_jvm_columns() { + // Order matters! + + g_col_heap_committed = new MemorySizeColumn("jvm", + "heap", "comm", "Java Heap Size, committed"); + g_col_heap_used = new MemorySizeColumn("jvm", + "heap", "used", "Java Heap Size, used"); + + g_col_metaspace_committed = new MemorySizeColumn("jvm", + "meta", "comm", "Meta Space Size (class+nonclass), committed"); + + g_col_metaspace_used = new MemorySizeColumn("jvm", + "meta", "used", "Meta Space Size (class+nonclass), used"); + + if (Metaspace::using_class_space()) { + g_col_classspace_committed = new MemorySizeColumn("jvm", + "meta", "csc", "Class Space Size, committed"); + g_col_classspace_used = new MemorySizeColumn("jvm", + "meta", "csu", "Class Space Size, used"); + } + + g_col_metaspace_cap_until_gc = new MemorySizeColumn("jvm", + "meta", "gctr", "GC threshold"); + + g_col_codecache_committed = new MemorySizeColumn("jvm", + NULL, "code", "Code cache, committed"); + + g_col_nmt_malloc = new MemorySizeColumn("jvm", + NULL, "mlc", "Memory malloced by hotspot (requires NMT)"); + + g_col_number_of_java_threads = new PlainValueColumn("jvm", + "jthr", "num", "Number of java threads"); + + g_col_number_of_java_threads_non_demon = new PlainValueColumn("jvm", + "jthr", "nd", "Number of non-demon java threads"); + + g_col_number_of_java_threads_created = new DeltaValueColumn("jvm", + "jthr", "cr", "Threads created"); + + g_col_size_thread_stacks = new MemorySizeColumn("jvm", + "jthr", "st", "Total reserved size of java thread stacks"); + + g_col_number_of_clds = new PlainValueColumn("jvm", + "cldg", "num", "Classloader Data"); + + g_col_number_of_anon_clds = new PlainValueColumn("jvm", + "cldg", "anon", "Anonymous CLD"); + + g_col_number_of_classes = new PlainValueColumn("jvm", + "cls", "num", "Classes (instance + array)"); + + g_col_number_of_class_loads = new DeltaValueColumn("jvm", + "cls", "ld", "Class loaded"); + + g_col_number_of_class_unloads = new DeltaValueColumn("jvm", + "cls", "uld", "Classes unloaded"); + + return true; +} + + +////////// class ValueSampler and childs ///////////////// + +template +static void set_value_in_sample(const Column* col, Sample* sample, T t) { + if (col != NULL) { + int idx = col->index(); + assert(ColumnList::the_list()->is_valid_column_index(idx), "Invalid column index"); + sample->set_value(idx, (value_t)t); + } +} + +class AddStackSizeThreadClosure: public ThreadClosure { + size_t _l; +public: + AddStackSizeThreadClosure() : ThreadClosure(), _l(0) {} + void do_thread(Thread* thread) { + _l += thread->stack_size(); + } + size_t get() const { return _l; } +}; + +static uint64_t accumulate_thread_stack_size() { +#if defined(LINUX) + // Do not iterate thread list and query stack size until 8212173 is completely solved. It is solved + // for Linux (possibly BSD); on the other platforms, one runs a miniscule but real risk of triggering + // the assert in Thread::stack_size(). + size_t l = 0; + AddStackSizeThreadClosure tc; + { + MutexLocker ml(Threads_lock); + Threads::threads_do(&tc); + } + return (uint64_t)tc.get(); +#else + return INVALID_VALUE; +#endif +} + +// Count CLDs +class CLDCounterClosure: public CLDClosure { +public: + int _cnt; + int _anon_cnt; + CLDCounterClosure() : _cnt(0), _anon_cnt(0) {} + void do_cld(ClassLoaderData* cld) { + _cnt ++; + if (cld->has_class_mirror_holder()) { + _anon_cnt ++; + } + } +}; + +static value_t get_bytes_malloced_by_jvm_via_sapjvm_mallstat() { + value_t result = INVALID_VALUE; + // SAPJVM plug in mallstat entry here. + return result; +} + +#if INCLUDE_NMT +static value_t get_bytes_malloced_by_jvm_via_nmt() { + value_t result = INVALID_VALUE; + if (MemTracker::tracking_level() != NMT_off) { + MutexLocker locker(MemTracker::query_lock()); + result = MallocMemorySummary::as_snapshot()->total(); + } + return result; +} +#endif // INCLUDE_NMT + +void sample_jvm_values(Sample* sample, bool avoid_locking) { + + // Note: if avoid_locking=true, skip values which need JVM-side locking. + + // Heap + if (!avoid_locking) { + size_t heap_cap = 0; + size_t heap_used = 0; + const CollectedHeap* const heap = Universe::heap(); + if (heap != NULL) { + MutexLocker hl(Heap_lock); + heap_cap = Universe::heap()->capacity(); + heap_used = Universe::heap()->used(); + } + set_value_in_sample(g_col_heap_committed, sample, heap_cap); + set_value_in_sample(g_col_heap_used, sample, heap_used); + } + + // Metaspace + set_value_in_sample(g_col_metaspace_committed, sample, MetaspaceUtils::committed_bytes()); + set_value_in_sample(g_col_metaspace_used, sample, MetaspaceUtils::used_bytes()); + + if (Metaspace::using_class_space()) { + set_value_in_sample(g_col_classspace_committed, sample, MetaspaceUtils::committed_bytes(Metaspace::ClassType)); + set_value_in_sample(g_col_classspace_used, sample, MetaspaceUtils::used_bytes(Metaspace::ClassType)); + } + + set_value_in_sample(g_col_metaspace_cap_until_gc, sample, MetaspaceGC::capacity_until_GC()); + + // Code cache + const size_t codecache_committed = CodeCache::capacity(); + set_value_in_sample(g_col_codecache_committed, sample, codecache_committed); + + // bytes malloced by JVM. Prefer sapjvm mallstat if available (less overhead, always-on). Fall back to NMT + // otherwise. + value_t bytes_malloced_by_jvm = get_bytes_malloced_by_jvm_via_sapjvm_mallstat(); +#if INCLUDE_NMT + if (bytes_malloced_by_jvm == INVALID_VALUE && !avoid_locking) { + bytes_malloced_by_jvm = get_bytes_malloced_by_jvm_via_nmt(); + } +#endif + set_value_in_sample(g_col_nmt_malloc, sample, bytes_malloced_by_jvm); + + // Java threads + set_value_in_sample(g_col_number_of_java_threads, sample, Threads::number_of_threads()); + set_value_in_sample(g_col_number_of_java_threads_non_demon, sample, Threads::number_of_non_daemon_threads()); + set_value_in_sample(g_col_number_of_java_threads_created, sample, counters::g_threads_created); + + // Java thread stack size + if (!avoid_locking) { + set_value_in_sample(g_col_size_thread_stacks, sample, accumulate_thread_stack_size()); + } + + // CLDG + if (!avoid_locking) { + CLDCounterClosure cl; + { + MutexLocker lck(ClassLoaderDataGraph_lock); + ClassLoaderDataGraph::cld_do(&cl); + } + set_value_in_sample(g_col_number_of_clds, sample, cl._cnt); + set_value_in_sample(g_col_number_of_anon_clds, sample, cl._anon_cnt); + } + + // Classes + set_value_in_sample(g_col_number_of_classes, sample, + ClassLoaderDataGraph::num_instance_classes() + ClassLoaderDataGraph::num_array_classes()); + set_value_in_sample(g_col_number_of_class_loads, sample, counters::g_classes_loaded); + set_value_in_sample(g_col_number_of_class_unloads, sample, counters::g_classes_unloaded); +} + +bool initialize() { + + g_vitals_lock = new Mutex(Mutex::nosafepoint, "Vitals Lock", Mutex::_safepoint_check_never); + + if (!ColumnList::initialize()) { + return false; + } + + // Order matters. First platform columns, then jvm columns. + if (!platform_columns_initialize()) { + return false; + } + + if (!add_jvm_columns()) { + return false; + } + + // -- Now the number of columns is known (and fixed). -- + + g_all_tables = new SampleTables(); + if (!g_all_tables) { + return false; + } + + if (!initialize_sampler_thread()) { + return false; + } + + return true; + +} + +void cleanup() { + if (g_sampler_thread != NULL) { + g_sampler_thread->stop(); + } +} + +void default_settings(print_info_t* out) { + out->raw = false; + out->csv = false; + out->no_legend = false; + out->reverse_ordering = false; + out->scale = 0; + out->sample_now = false; +} + +void print_report(outputStream* st, const print_info_t* pinfo) { + + st->print("Vitals:"); + + if (ColumnList::the_list() == NULL) { + st->print_cr(" (unavailable)"); + return; + } + + st->cr(); + + print_info_t info; + if (pinfo != NULL) { + info = *pinfo; + } else { + default_settings(&info); + } + + // Print legend at the top (omit if suppressed on command line, or in csv mode). + if (info.no_legend == false && info.csv == false) { + print_legend(st, &info); + st->cr(); + } + + // If we are to sample the current values at print time, do that and print them too. + Sample* sample_now = NULL; + if (info.sample_now) { + sample_now = Sample::allocate(); + sample_values(sample_now, true /* never lock for now sample - be safe */ ); + } + + g_all_tables->print_all(st, &info, sample_now); + + os::free(sample_now); + +} + +// Dump both textual and csv style reports to two files, "sapmachine_vitals_.txt" and "sapmachine_vitals_.csv". +// If these files exist, they are overwritten. +void dump_reports() { + + static const char* file_prefix = "sapmachine_vitals_"; + char vitals_file_name[1024]; + + if (VitalsFile != NULL) { + os::snprintf(vitals_file_name, sizeof(vitals_file_name), "%s.txt", VitalsFile); + } else { + os::snprintf(vitals_file_name, sizeof(vitals_file_name), "%s%d.txt", file_prefix, os::current_process_id()); + } + + // Note: we print two reports, both in reverse order (oldest to youngest). One in text form, one as csv. + + ::printf("Dumping Vitals to %s\n", vitals_file_name); + { + fileStream fs(vitals_file_name); + static const sapmachine_vitals::print_info_t settings = { + false, // raw + false, // csv + false, // no_legend + true, // reverse_ordering + 0, // scale + true // sample_now + }; + print_report(&fs, &settings); + } + + if (VitalsFile != NULL) { + os::snprintf(vitals_file_name, sizeof(vitals_file_name), "%s.csv", VitalsFile); + } else { + os::snprintf(vitals_file_name, sizeof(vitals_file_name), "%s%d.csv", file_prefix, os::current_process_id()); + } + ::printf("Dumping Vitals csv to %s\n", vitals_file_name); + { + fileStream fs(vitals_file_name); + static const sapmachine_vitals::print_info_t settings = { + false, // raw + true, // csv + false, // no_legend + true, // reverse_ordering + 1 * K, // scale + true // sample_now + }; + print_report(&fs, &settings); + } + +} + +// For printing in thread lists only. +const Thread* samplerthread() { return g_sampler_thread; } + +} // namespace sapmachine_vitals diff --git a/src/hotspot/share/vitals/vitals.hpp b/src/hotspot/share/vitals/vitals.hpp new file mode 100644 index 00000000000..e88be0affeb --- /dev/null +++ b/src/hotspot/share/vitals/vitals.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef HOTSPOT_SHARE_VITALS_VITALS_HPP +#define HOTSPOT_SHARE_VITALS_VITALS_HPP + +#include "utilities/globalDefinitions.hpp" + +class outputStream; +class Thread; + +namespace sapmachine_vitals { + + bool initialize(); + void cleanup(); + + struct print_info_t { + bool raw; + bool csv; + // Omit printing a legend. + bool no_legend; + // Reverse printing order (default: youngest-to-oldest; reversed: oldest-to-youngest) + bool reverse_ordering; + + size_t scale; + + // if true, sample and print the current values too. If false, + // just print the sample tables. + bool sample_now; + + }; + + void default_settings(print_info_t* out); + + // Print report to stream. Leave print_info NULL for default settings. + void print_report(outputStream* st, const print_info_t* print_info = NULL); + + // Dump both textual and csv style reports to two files, "vitals_.txt" and "vitals_.csv". + // If these files exist, they are overwritten. + void dump_reports(); + + // For printing in thread lists only. + const Thread* samplerthread(); + + namespace counters { + void inc_classes_loaded(size_t count); + void inc_classes_unloaded(size_t count); + void inc_threads_created(size_t count); + }; + +}; + +#endif /* HOTSPOT_SHARE_VITALS_VITALS_HPP */ diff --git a/src/hotspot/share/vitals/vitalsDCmd.cpp b/src/hotspot/share/vitals/vitalsDCmd.cpp new file mode 100644 index 00000000000..226aaa4e660 --- /dev/null +++ b/src/hotspot/share/vitals/vitalsDCmd.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, 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. + * + */ + + +#include "precompiled.hpp" +#include "memory/resourceArea.hpp" +#include "utilities/ostream.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals.hpp" +#include "vitals/vitalsDCmd.hpp" + +namespace sapmachine_vitals { + +VitalsDCmd::VitalsDCmd(outputStream* output, bool heap) + : DCmdWithParser(output, heap), + _scale("scale", "Memory usage in which to scale. Valid values are: k, m, g (fixed scale) " + "or \"dynamic\" for a dynamically chosen scale.", + "STRING", false, "dynamic"), + _csv("csv", "csv format.", "BOOLEAN", false, "false"), + _no_legend("no-legend", "Omit legend.", "BOOLEAN", false, "false"), + _reverse("reverse", "Reverse printing order.", "BOOLEAN", false, "false"), + _raw("raw", "Print raw values.", "BOOLEAN", false, "false"), + _sample_now("now", "Sample now values", "BOOLEAN", false, "false") +{ + _dcmdparser.add_dcmd_option(&_scale); + _dcmdparser.add_dcmd_option(&_csv); + _dcmdparser.add_dcmd_option(&_no_legend); + _dcmdparser.add_dcmd_option(&_reverse); + _dcmdparser.add_dcmd_option(&_raw); + _dcmdparser.add_dcmd_option(&_sample_now); +} + +int VitalsDCmd::num_arguments() { + ResourceMark rm; + VitalsDCmd* dcmd = new VitalsDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } else { + return 0; + } +} + +static bool scale_from_name(const char* scale, size_t* out) { + if (strcasecmp(scale, "dynamic") == 0) { + *out = 0; + } else if (strcasecmp(scale, "1") == 0 || strcasecmp(scale, "b") == 0) { + *out = 1; + } else if (strcasecmp(scale, "kb") == 0 || strcasecmp(scale, "k") == 0) { + *out = K; + } else if (strcasecmp(scale, "mb") == 0 || strcasecmp(scale, "m") == 0) { + *out = M; + } else if (strcasecmp(scale, "gb") == 0 || strcasecmp(scale, "g") == 0) { + *out = G; + } else { + return false; // Invalid value + } + return true; +} + +void VitalsDCmd::execute(DCmdSource source, TRAPS) { + sapmachine_vitals::print_info_t info; + sapmachine_vitals::default_settings(&info); + if (!scale_from_name(_scale.value(), &(info.scale))) { + output()->print_cr("Invalid scale: \"%s\".", _scale.value()); + return; + } + info.csv = _csv.value(); + info.no_legend = _no_legend.value(); + info.reverse_ordering = _reverse.value(); + info.raw = _raw.value(); + info.sample_now = _sample_now.value(); + + sapmachine_vitals::print_report(output(), &info); +} + +}; // namespace sapmachine_vitals diff --git a/src/hotspot/share/vitals/vitalsDCmd.hpp b/src/hotspot/share/vitals/vitalsDCmd.hpp new file mode 100644 index 00000000000..f429fcf688e --- /dev/null +++ b/src/hotspot/share/vitals/vitalsDCmd.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + + +#ifndef HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP +#define HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP + +#include "services/diagnosticCommand.hpp" + +namespace sapmachine_vitals { + +class VitalsDCmd : public DCmdWithParser { +protected: + DCmdArgument _scale; + DCmdArgument _csv; + DCmdArgument _no_legend; + DCmdArgument _reverse; + DCmdArgument _raw; + DCmdArgument _sample_now; +public: + VitalsDCmd(outputStream* output, bool heap); + static const char* name() { + return "VM.vitals"; + } + static const char* description() { + return "Print Vitals."; + } + static const char* impact() { + return "Low."; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +}; // namespace sapmachine_vitals + +#endif /* HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP */ + diff --git a/src/hotspot/share/vitals/vitals_internals.hpp b/src/hotspot/share/vitals/vitals_internals.hpp new file mode 100644 index 00000000000..ab3729c3fef --- /dev/null +++ b/src/hotspot/share/vitals/vitals_internals.hpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2019, 2021 SAP SE. All rights reserved. + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP +#define HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP + +#include "memory/allocation.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "vitals/vitals.hpp" + + + +namespace sapmachine_vitals { + + typedef uint64_t value_t; +#define INVALID_VALUE ((value_t)UINT64_MAX) + + class Sample { + DEBUG_ONLY(int _num;) + time_t _timestamp; + value_t _values[1]; // var sized + public: + static int num_values(); + static size_t size_in_bytes(); + static Sample* allocate(); + + void reset(); + void set_value(int index, value_t v); + void set_timestamp(time_t t); + DEBUG_ONLY(void set_num(int n);) + + value_t value(int index) const; + time_t timestamp() const { return _timestamp; } + DEBUG_ONLY(int num() const { return _num; }) + }; + + class ColumnList; + + class Column: public CHeapObj { + friend class ColumnList; + + const char* const _category; + const char* const _header; // optional. May be NULL. + const char* const _name; + const char* const _description; + const bool _delta; + + // The following members are fixed by ColumnList when the Column is added to it. + Column* _next; // next column in table + int _idx; // position in table + int _idx_cat; // position in category + int _idx_hdr; // position under its header (if any, 0 otherwise) + + int do_print(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + + protected: + + Column(const char* category, const char* header, const char* name, const char* description, bool delta); + + // Child classes implement this. + // output stream can be NULL; in that case, method shall return number of characters it would have printed. + virtual int do_print0(outputStream* os, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const = 0; + + public: + + const char* category() const { return _category; } + const char* header() const { return _header; } + const char* name() const { return _name; } + const char* description() const { return _description; } + + void print_value(outputStream* os, value_t value, value_t last_value, + int last_value_age, int min_width, const print_info_t* pi) const; + + // Returns the number of characters this value needs to be printed. + int calc_print_size(value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + + // Returns the index (the position in the table) of this column. + int index() const { return _idx; } + int index_within_category_section() const { return _idx_cat; } + int index_within_header_section() const { return _idx_hdr; } + + const Column* next () const { return _next; } + + virtual bool is_delta() const { return _delta; } + virtual bool is_memory_size() const { return false; } + + }; + + // Some standard column types + + class PlainValueColumn: public Column { + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + public: + PlainValueColumn(const char* category, const char* header, const char* name, const char* description) + : Column(category, header, name, description, false) + {} + }; + + class DeltaValueColumn: public Column { + const bool _show_only_positive; + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + public: + // only_positive: only positive deltas are shown, negative deltas are supressed + DeltaValueColumn(const char* category, const char* header, const char* name, const char* description, + bool show_only_positive = true) + : Column(category, header, name, description, true) + , _show_only_positive(show_only_positive) + {} + }; + + class MemorySizeColumn: public Column { + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + public: + MemorySizeColumn(const char* category, const char* header, const char* name, const char* description) + : Column(category, header, name, description, false) + {} + bool is_memory_size() const { return true; } + }; + + class DeltaMemorySizeColumn: public Column { + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + public: + DeltaMemorySizeColumn(const char* category, const char* header, const char* name, const char* description) + : Column(category, header, name, description, false) + {} + }; + + class TimeStampColumn: public Column { + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + public: + TimeStampColumn(const char* category, const char* header, const char* name, const char* description) + : Column(category, header, name, description, false) + {} + }; + + ////// ColumnList: a singleton class holding all information about all columns + + class ColumnList: public CHeapObj { + + Column* _first, *_last; + int _num_columns; + + static ColumnList* _the_list; + + public: + + ColumnList() + : _first(NULL), _last(NULL), _num_columns(0) + {} + + const Column* first() const { return _first; } + int num_columns() const { return _num_columns; } + + void add_column(Column* column); + + static ColumnList* the_list () { return _the_list; } + + static bool initialize(); + +#ifdef ASSERT + bool is_valid_column_index(int idx) { + return idx >= 0 && idx < _num_columns; + } +#endif + + }; + + // Implemented by platform specific + bool platform_columns_initialize(); + + void sample_platform_values(Sample* sample); + void sample_jvm_values(Sample* sample, bool avoid_locking); + +}; // namespace sapmachine_vitals + +#endif /* HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP */ diff --git a/test/hotspot/gtest/vitals/test_vitals.cpp b/test/hotspot/gtest/vitals/test_vitals.cpp new file mode 100644 index 00000000000..12553af1570 --- /dev/null +++ b/test/hotspot/gtest/vitals/test_vitals.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 SAP SE. 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. + * + */ + +#include "precompiled.hpp" +#include "runtime/globals.hpp" +#include "utilities/debug.hpp" +#include "utilities/ostream.hpp" +#include "utilities/globalDefinitions.hpp" +#include "unittest.hpp" +#include "vitals/vitals.hpp" + +//#define LOG(s) tty->print_raw(s); +#define LOG(s) + +TEST_VM(vitals, report_with_explicit_default_settings) { + char tmp[64*K]; + stringStream ss(tmp, sizeof(tmp)); + sapmachine_vitals::print_info_t info; + ::memset(&info, 0xBB, sizeof(info)); + sapmachine_vitals::default_settings(&info); + sapmachine_vitals::print_report(&ss, &info); + LOG(tmp); + if (EnableVitals) { + ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)NULL); + } +} + +TEST_VM(vitals, report_with_implicit_default_settings) { + char tmp[64*K]; + stringStream ss(tmp, sizeof(tmp)); + sapmachine_vitals::print_report(&ss, NULL); + LOG(tmp); + if (EnableVitals) { + ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)NULL); + } +} + +TEST_VM(vitals, report_with_nownow) { + char tmp[64*K]; + stringStream ss(tmp, sizeof(tmp)); + sapmachine_vitals::print_info_t info; + ::memset(&info, 0xBB, sizeof(info)); + sapmachine_vitals::default_settings(&info); + info.sample_now = true; + for (int i = 0; i < 100; i ++) { + ss.reset(); + sapmachine_vitals::print_report(&ss, NULL); + if (EnableVitals) { + ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)NULL); + } + } +} diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index d0b34218f6b..9ac09f0f022 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -460,7 +460,15 @@ tier1_serviceability = \ -serviceability/sa/TestJmapCore.java \ -serviceability/sa/TestJmapCoreMetaspace.java +# SapMachine 2019-02-24 : Add tests where SAPMachine has different behavior to tier1, +# or which tests downstream-only features. +tier1_sapmachine = \ + runtime/Vitals + +# SapMachine 2019-02-24 : Add tests where SAPMachine has different behavior to tier1, +# or which tests downstream-only features. tier1 = \ + :tier1_sapmachine \ :tier1_common \ :tier1_compiler \ :tier1_gc \ diff --git a/test/hotspot/jtreg/runtime/Vitals/TestVitalsAtExit.java b/test/hotspot/jtreg/runtime/Vitals/TestVitalsAtExit.java new file mode 100644 index 00000000000..90ce5a8bcce --- /dev/null +++ b/test/hotspot/jtreg/runtime/Vitals/TestVitalsAtExit.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, SAP SE. 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 TestVitalsAtExit + * @summary Test verifies that -XX:+PrintVitalsAtExit prints vitals at exit. + * @library /test/lib + * @run driver TestVitalsAtExit run print + */ + +/* + * @test TestVitalsAtExit + * @summary Test verifies that -XX:+DumpVitalsAtExit works + * @library /test/lib + * @run driver TestVitalsAtExit run dump + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; + +public class TestVitalsAtExit { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + try { + Thread.sleep(2000); + } catch (InterruptedException err) { + } + return; + } + if (args[0].equals("print")) { + testPrint(); + } else { + testDump(); + } + } + + static void testPrint() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-XX:+EnableVitals", + "-XX:+PrintVitalsAtExit", + "-XX:MaxMetaspaceSize=16m", + "-Xmx128m", + TestVitalsAtExit.class.getName()); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.stdoutShouldNotBeEmpty(); + output.shouldContain("--jvm--"); + } + + static void testDump() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-XX:+EnableVitals", + "-XX:+DumpVitalsAtExit", + "-XX:VitalsFile=abcd", + "-XX:MaxMetaspaceSize=16m", + "-Xmx128m", + TestVitalsAtExit.class.getName()); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.stdoutShouldNotBeEmpty(); + output.shouldContain("Dumping Vitals to abcd.txt"); + output.shouldContain("Dumping Vitals csv to abcd.csv"); + File dump = new File("abcd.txt"); + Asserts.assertTrue(dump.exists() && dump.isFile(), + "Could not find abcd.txt"); + File dump2 = new File("abcd.csv"); + Asserts.assertTrue(dump2.exists() && dump2.isFile(), + "Could not find abcd.csv"); + } + +} diff --git a/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdStressTest.java b/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdStressTest.java new file mode 100644 index 00000000000..a3bc94c2757 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdStressTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020, SAP SE. 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 of diagnostic command VM.vitals + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run testng/othervm -XX:VitalsSampleInterval=1 VitalsDCmdStressTest + */ + +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import org.testng.annotations.Test; + +public class VitalsDCmdStressTest { + + final long runtime_secs = 30; + + public void run_once(CommandExecutor executor, boolean silent) { + OutputAnalyzer output = executor.execute("VM.vitals now", silent); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldContain("Now"); + } + + public void run(CommandExecutor executor) { + // Let vitals run a while and bombard it with report requests which include "now" sampling + long t1 = System.currentTimeMillis(); + long t2 = t1 + runtime_secs * 1000; + int invocations = 0; + while (System.currentTimeMillis() < t2) { + run_once(executor, true); // run silent to avoid filling logs with garbage output + invocations ++; + } + + // One last time run with silent off + run_once(executor, false); + invocations ++; + + System.out.println("Called " + invocations + " times."); + } + + @Test + public void jmx() { + run(new JMXExecutor()); + // wait two seconds to collect some samples, then repeat with filled tables + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + run(new JMXExecutor()); + } + +} diff --git a/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdTest.java b/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdTest.java new file mode 100644 index 00000000000..374104cef53 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Vitals/VitalsDCmdTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, SAP SE. 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 of diagnostic command VM.vitals + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run testng/othervm -XX:VitalsSampleInterval=1 VitalsDCmdTest + */ + +import org.testng.Assert; +import org.testng.annotations.Test; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; + +public class VitalsDCmdTest { + + public void run(CommandExecutor executor) { + + OutputAnalyzer output = executor.execute("VM.vitals"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldNotContain("Now"); // off by default + + output = executor.execute("VM.vitals reverse"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldNotContain("Now"); // off by default + + output = executor.execute("VM.vitals scale=m"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldNotContain("Now"); // off by default + + output = executor.execute("VM.vitals scale=1"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldNotContain("Now"); // off by default + + output = executor.execute("VM.vitals raw"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldNotContain("Now"); // off by default + + output = executor.execute("VM.vitals now"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldContain("Now"); + + output = executor.execute("VM.vitals reverse now"); + output.shouldContain("--jvm--"); + output.shouldContain("--heap--"); + output.shouldContain("--meta--"); + output.shouldContain("Now"); + + output = executor.execute("VM.vitals csv"); + output.shouldContain("jvm-heap-comm,jvm-heap-used,jvm-meta-comm,jvm-meta-used"); + + output = executor.execute("VM.vitals csv now"); + output.shouldContain("jvm-heap-comm,jvm-heap-used,jvm-meta-comm,jvm-meta-used"); + output.shouldContain("Now"); + + output = executor.execute("VM.vitals csv reverse"); + output.shouldContain("jvm-heap-comm,jvm-heap-used,jvm-meta-comm,jvm-meta-used"); + + output = executor.execute("VM.vitals csv reverse raw"); + output.shouldContain("jvm-heap-comm,jvm-heap-used,jvm-meta-comm,jvm-meta-used"); + + output = executor.execute("VM.vitals csv now reverse raw"); + output.shouldContain("jvm-heap-comm,jvm-heap-used,jvm-meta-comm,jvm-meta-used"); + output.shouldContain("Now"); + + } + + @Test + public void jmx() { + run(new JMXExecutor()); + // wait two seconds to collect some samples, then repeat with filled tables + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + run(new JMXExecutor()); + } + +}