679 lines
28 KiB
Diff
679 lines
28 KiB
Diff
From 1b97a08d822b7e2388ded07c696fffe70b39697a Mon Sep 17 00:00:00 2001
|
|
From: eapen <zhangyipeng7@huawei.com>
|
|
Date: Mon, 12 Dec 2022 17:40:09 +0800
|
|
Subject: [PATCH 12/33] I68TO2: 8268893: jcmd to trim the glibc heap
|
|
---
|
|
hotspot/src/os/linux/vm/os_linux.cpp | 57 ++++---
|
|
hotspot/src/os/linux/vm/os_linux.hpp | 17 ++
|
|
hotspot/src/os/linux/vm/trimCHeapDCmd.cpp | 77 +++++++++
|
|
hotspot/src/os/linux/vm/trimCHeapDCmd.hpp | 52 ++++++
|
|
.../src/share/vm/services/diagnosticCommand.cpp | 7 +
|
|
.../test/serviceability/dcmd/TrimLibcHeapTest.java | 53 ++++++
|
|
.../oracle/java/testlibrary/CommandExecutor.java | 73 ++++++++
|
|
.../java/testlibrary/CommandExecutorException.java | 36 ++++
|
|
.../com/oracle/java/testlibrary/JMXExecutor.java | 185 +++++++++++++++++++++
|
|
9 files changed, 532 insertions(+), 25 deletions(-)
|
|
create mode 100644 hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
|
|
create mode 100644 hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
|
|
create mode 100644 hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
|
|
create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
|
|
create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
|
|
create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java
|
|
|
|
diff --git a/hotspot/src/os/linux/vm/os_linux.cpp b/hotspot/src/os/linux/vm/os_linux.cpp
|
|
index 1a3504f..c687b1c 100644
|
|
--- a/hotspot/src/os/linux/vm/os_linux.cpp
|
|
+++ b/hotspot/src/os/linux/vm/os_linux.cpp
|
|
@@ -2290,44 +2290,51 @@ void os::Linux::print_system_memory_info(outputStream* st) {
|
|
st->cr();
|
|
}
|
|
|
|
-void os::Linux::print_process_memory_info(outputStream* st) {
|
|
-
|
|
- st->print_cr("Process Memory:");
|
|
-
|
|
- // Print virtual and resident set size; peak values; swap; and for
|
|
- // rss its components if the kernel is recent enough.
|
|
- ssize_t vmsize = -1, vmpeak = -1, vmswap = -1,
|
|
- vmrss = -1, vmhwm = -1, rssanon = -1, rssfile = -1, rssshmem = -1;
|
|
- const int num_values = 8;
|
|
- int num_found = 0;
|
|
+bool os::Linux::query_process_memory_info(os::Linux::meminfo_t* info) {
|
|
FILE* f = ::fopen("/proc/self/status", "r");
|
|
+ const int num_values = sizeof(os::Linux::meminfo_t) / sizeof(size_t);
|
|
+ int num_found = 0;
|
|
char buf[256];
|
|
+ info->vmsize = info->vmpeak = info->vmrss = info->vmhwm = info->vmswap =
|
|
+ info->rssanon = info->rssfile = info->rssshmem = -1;
|
|
if (f != NULL) {
|
|
while (::fgets(buf, sizeof(buf), f) != NULL && num_found < num_values) {
|
|
- if ( (vmsize == -1 && sscanf(buf, "VmSize: " SSIZE_FORMAT " kB", &vmsize) == 1) ||
|
|
- (vmpeak == -1 && sscanf(buf, "VmPeak: " SSIZE_FORMAT " kB", &vmpeak) == 1) ||
|
|
- (vmswap == -1 && sscanf(buf, "VmSwap: " SSIZE_FORMAT " kB", &vmswap) == 1) ||
|
|
- (vmhwm == -1 && sscanf(buf, "VmHWM: " SSIZE_FORMAT " kB", &vmhwm) == 1) ||
|
|
- (vmrss == -1 && sscanf(buf, "VmRSS: " SSIZE_FORMAT " kB", &vmrss) == 1) ||
|
|
- (rssanon == -1 && sscanf(buf, "RssAnon: " SSIZE_FORMAT " kB", &rssanon) == 1) ||
|
|
- (rssfile == -1 && sscanf(buf, "RssFile: " SSIZE_FORMAT " kB", &rssfile) == 1) ||
|
|
- (rssshmem == -1 && sscanf(buf, "RssShmem: " SSIZE_FORMAT " kB", &rssshmem) == 1)
|
|
+ if ( (info->vmsize == -1 && sscanf(buf, "VmSize: " SSIZE_FORMAT " kB", &info->vmsize) == 1) ||
|
|
+ (info->vmpeak == -1 && sscanf(buf, "VmPeak: " SSIZE_FORMAT " kB", &info->vmpeak) == 1) ||
|
|
+ (info->vmswap == -1 && sscanf(buf, "VmSwap: " SSIZE_FORMAT " kB", &info->vmswap) == 1) ||
|
|
+ (info->vmhwm == -1 && sscanf(buf, "VmHWM: " SSIZE_FORMAT " kB", &info->vmhwm) == 1) ||
|
|
+ (info->vmrss == -1 && sscanf(buf, "VmRSS: " SSIZE_FORMAT " kB", &info->vmrss) == 1) ||
|
|
+ (info->rssanon == -1 && sscanf(buf, "RssAnon: " SSIZE_FORMAT " kB", &info->rssanon) == 1) || // Needs Linux 4.5
|
|
+ (info->rssfile == -1 && sscanf(buf, "RssFile: " SSIZE_FORMAT " kB", &info->rssfile) == 1) || // Needs Linux 4.5
|
|
+ (info->rssshmem == -1 && sscanf(buf, "RssShmem: " SSIZE_FORMAT " kB", &info->rssshmem) == 1) // Needs Linux 4.5
|
|
)
|
|
{
|
|
num_found ++;
|
|
}
|
|
}
|
|
fclose(f);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
+void os::Linux::print_process_memory_info(outputStream* st) {
|
|
|
|
- st->print_cr("Virtual Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", vmsize, vmpeak);
|
|
- st->print("Resident Set Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", vmrss, vmhwm);
|
|
- if (rssanon != -1) { // requires kernel >= 4.5
|
|
+ st->print_cr("Process Memory:");
|
|
+
|
|
+ // Print virtual and resident set size; peak values; swap; and for
|
|
+ // rss its components if the kernel is recent enough.
|
|
+ meminfo_t info;
|
|
+ if (query_process_memory_info(&info)) {
|
|
+ st->print_cr("Virtual Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", info.vmsize, info.vmpeak);
|
|
+ st->print("Resident Set Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", info.vmrss, info.vmhwm);
|
|
+ if (info.rssanon != -1) { // requires kernel >= 4.5
|
|
st->print(" (anon: " SSIZE_FORMAT "K, file: " SSIZE_FORMAT "K, shmem: " SSIZE_FORMAT "K)",
|
|
- rssanon, rssfile, rssshmem);
|
|
+ info.rssanon, info.rssfile, info.rssshmem);
|
|
}
|
|
st->cr();
|
|
- if (vmswap != -1) { // requires kernel >= 2.6.34
|
|
- st->print_cr("Swapped out: " SSIZE_FORMAT "K", vmswap);
|
|
+ if (info.vmswap != -1) { // requires kernel >= 2.6.34
|
|
+ st->print_cr("Swapped out: " SSIZE_FORMAT "K", info.vmswap);
|
|
}
|
|
} else {
|
|
st->print_cr("Could not open /proc/self/status to get process memory related information");
|
|
@@ -2344,7 +2351,7 @@ void os::Linux::print_process_memory_info(outputStream* st) {
|
|
const size_t total_allocated = (size_t)(unsigned)mi.uordblks;
|
|
st->print("C-Heap outstanding allocations: " SIZE_FORMAT "K", total_allocated / K);
|
|
// Since mallinfo members are int, glibc values may have wrapped. Warn about this.
|
|
- if ((vmrss * K) > UINT_MAX && (vmrss * K) > (total_allocated + UINT_MAX)) {
|
|
+ if ((info.vmrss * K) > UINT_MAX && (info.vmrss * K) > (total_allocated + UINT_MAX)) {
|
|
st->print(" (may have wrapped)");
|
|
}
|
|
st->cr();
|
|
diff --git a/hotspot/src/os/linux/vm/os_linux.hpp b/hotspot/src/os/linux/vm/os_linux.hpp
|
|
index 066b03a..2c4efff 100644
|
|
--- a/hotspot/src/os/linux/vm/os_linux.hpp
|
|
+++ b/hotspot/src/os/linux/vm/os_linux.hpp
|
|
@@ -243,6 +243,23 @@ class Linux {
|
|
public:
|
|
static pthread_condattr_t* condAttr() { return _condattr; }
|
|
|
|
+ // Output structure for query_process_memory_info()
|
|
+ struct meminfo_t {
|
|
+ ssize_t vmsize; // current virtual size
|
|
+ ssize_t vmpeak; // peak virtual size
|
|
+ ssize_t vmrss; // current resident set size
|
|
+ ssize_t vmhwm; // peak resident set size
|
|
+ ssize_t vmswap; // swapped out
|
|
+ ssize_t rssanon; // resident set size (anonymous mappings, needs 4.5)
|
|
+ ssize_t rssfile; // resident set size (file mappings, needs 4.5)
|
|
+ ssize_t rssshmem; // resident set size (shared mappings, needs 4.5)
|
|
+ };
|
|
+
|
|
+ // Attempts to query memory information about the current process and return it in the output structure.
|
|
+ // May fail (returns false) or succeed (returns true) but not all output fields are available; unavailable
|
|
+ // fields will contain -1.
|
|
+ static bool query_process_memory_info(meminfo_t* info);
|
|
+
|
|
// Stack repair handling
|
|
|
|
// none present
|
|
diff --git a/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
|
|
new file mode 100644
|
|
index 0000000..95d03d9
|
|
--- /dev/null
|
|
+++ b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
|
|
@@ -0,0 +1,77 @@
|
|
+/*
|
|
+ * Copyright (c) 2021 SAP SE. All rights reserved.
|
|
+ * Copyright (c) 2021, 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/ostream.hpp"
|
|
+#include "trimCHeapDCmd.hpp"
|
|
+
|
|
+#include <malloc.h>
|
|
+
|
|
+void TrimCLibcHeapDCmd::execute(DCmdSource source, TRAPS) {
|
|
+#ifdef __GLIBC__
|
|
+ stringStream ss_report(1024); // Note: before calling trim
|
|
+
|
|
+ os::Linux::meminfo_t info1;
|
|
+ os::Linux::meminfo_t info2;
|
|
+ // Query memory before...
|
|
+ bool have_info1 = os::Linux::query_process_memory_info(&info1);
|
|
+
|
|
+ _output->print_cr("Attempting trim...");
|
|
+ ::malloc_trim(0);
|
|
+ _output->print_cr("Done.");
|
|
+
|
|
+ // ...and after trim.
|
|
+ bool have_info2 = os::Linux::query_process_memory_info(&info2);
|
|
+
|
|
+ // Print report both to output stream as well to UL
|
|
+ bool wrote_something = false;
|
|
+ if (have_info1 && have_info2) {
|
|
+ if (info1.vmsize != -1 && info2.vmsize != -1) {
|
|
+ ss_report.print_cr("Virtual size before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
|
|
+ info1.vmsize, info2.vmsize, (info2.vmsize - info1.vmsize));
|
|
+ wrote_something = true;
|
|
+ }
|
|
+ if (info1.vmrss != -1 && info2.vmrss != -1) {
|
|
+ ss_report.print_cr("RSS before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
|
|
+ info1.vmrss, info2.vmrss, (info2.vmrss - info1.vmrss));
|
|
+ wrote_something = true;
|
|
+ }
|
|
+ if (info1.vmswap != -1 && info2.vmswap != -1) {
|
|
+ ss_report.print_cr("Swap before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
|
|
+ info1.vmswap, info2.vmswap, (info2.vmswap - info1.vmswap));
|
|
+ wrote_something = true;
|
|
+ }
|
|
+ }
|
|
+ if (!wrote_something) {
|
|
+ ss_report.print_raw("No details available.");
|
|
+ }
|
|
+
|
|
+ _output->print_raw(ss_report.base());
|
|
+#else
|
|
+ _output->print_cr("Not available.");
|
|
+#endif
|
|
+}
|
|
diff --git a/hotspot/src/os/linux/vm/trimCHeapDCmd.hpp b/hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
|
|
new file mode 100644
|
|
index 0000000..4c5b5cc
|
|
--- /dev/null
|
|
+++ b/hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
|
|
@@ -0,0 +1,52 @@
|
|
+/*
|
|
+ * Copyright (c) 2021 SAP SE. All rights reserved.
|
|
+ * Copyright (c) 2021, 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 OS_LINUX_TRIMCHEAPDCMD_HPP
|
|
+#define OS_LINUX_TRIMCHEAPDCMD_HPP
|
|
+
|
|
+#include "services/diagnosticCommand.hpp"
|
|
+
|
|
+class outputStream;
|
|
+
|
|
+class TrimCLibcHeapDCmd : public DCmd {
|
|
+public:
|
|
+ TrimCLibcHeapDCmd(outputStream* output, bool heap) : DCmd(output, heap) {}
|
|
+ static const char* name() {
|
|
+ return "System.trim_native_heap";
|
|
+ }
|
|
+ static const char* description() {
|
|
+ return "Attempts to free up memory by trimming the C-heap.";
|
|
+ }
|
|
+ static const char* impact() {
|
|
+ return "Low";
|
|
+ }
|
|
+ static const JavaPermission permission() {
|
|
+ JavaPermission p = { "java.lang.management.ManagementPermission", "control", NULL };
|
|
+ return p;
|
|
+ }
|
|
+ virtual void execute(DCmdSource source, TRAPS);
|
|
+};
|
|
+
|
|
+#endif // OS_LINUX_TRIMCHEAPDCMD_HPP
|
|
diff --git a/hotspot/src/share/vm/services/diagnosticCommand.cpp b/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
index 358ec6e..60417b5 100644
|
|
--- a/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
+++ b/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
@@ -36,6 +36,10 @@
|
|
#include "utilities/macros.hpp"
|
|
#include "oops/objArrayOop.hpp"
|
|
|
|
+#ifdef LINUX
|
|
+#include "trimCHeapDCmd.hpp"
|
|
+#endif
|
|
+
|
|
PRAGMA_FORMAT_MUTE_WARNINGS_FOR_GCC
|
|
|
|
void DCmdRegistrant::register_dcmds(){
|
|
@@ -65,6 +69,9 @@ void DCmdRegistrant::register_dcmds(){
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RotateGCLogDCmd>(full_export, true, false));
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));
|
|
+#ifdef LINUX
|
|
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<TrimCLibcHeapDCmd>(full_export, true, false));
|
|
+#endif // LINUX
|
|
|
|
// Enhanced JMX Agent Support
|
|
// These commands won't be exported via the DiagnosticCommandMBean until an
|
|
diff --git a/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
|
|
new file mode 100644
|
|
index 0000000..0fe8e35
|
|
--- /dev/null
|
|
+++ b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
|
|
@@ -0,0 +1,53 @@
|
|
+/*
|
|
+ * Copyright (c) 2021 SAP SE. All rights reserved.
|
|
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
|
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
+ *
|
|
+ * This code is free software; you can redistribute it and/or modify it
|
|
+ * under the terms of the GNU General Public License version 2 only, as
|
|
+ * published by the Free Software Foundation.
|
|
+ *
|
|
+ * This code is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
+ * version 2 for more details (a copy is included in the LICENSE file that
|
|
+ * accompanied this code).
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License version
|
|
+ * 2 along with this work; if not, write to the Free Software Foundation,
|
|
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
+ *
|
|
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
+ * or visit www.oracle.com if you need additional information or have any
|
|
+ * questions.
|
|
+ */
|
|
+
|
|
+import org.testng.annotations.Test;
|
|
+import com.oracle.java.testlibrary.*;
|
|
+
|
|
+/*
|
|
+ * @test
|
|
+ * @summary Test of diagnostic command VM.trim_libc_heap
|
|
+ * @library /testlibrary
|
|
+ * @requires os.family == "linux"
|
|
+ * @modules java.base/jdk.internal.misc
|
|
+ * java.compiler
|
|
+ * java.management
|
|
+ * jdk.internal.jvmstat/sun.jvmstat.monitor
|
|
+ * @run testng TrimLibcHeapTest
|
|
+ */
|
|
+public class TrimLibcHeapTest {
|
|
+ public void run(CommandExecutor executor) {
|
|
+ OutputAnalyzer output = executor.execute("System.trim_native_heap");
|
|
+ output.reportDiagnosticSummary();
|
|
+ output.shouldMatch("(Done|Not available)"); // Not available could happen on Linux + non-glibc (eg. muslc)
|
|
+ if (output.firstMatch("Done") != null) {
|
|
+ output.shouldMatch("(Virtual size before|RSS before|Swap before|No details available)");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void jmx() {
|
|
+ run(new JMXExecutor());
|
|
+ }
|
|
+}
|
|
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
|
|
new file mode 100644
|
|
index 0000000..e95a437
|
|
--- /dev/null
|
|
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
|
|
@@ -0,0 +1,73 @@
|
|
+/*
|
|
+ * Copyright (c) 2015, 2016, 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.
|
|
+ */
|
|
+
|
|
+package com.oracle.java.testlibrary;
|
|
+
|
|
+/**
|
|
+ * Abstract base class for Diagnostic Command executors
|
|
+ */
|
|
+public abstract class CommandExecutor {
|
|
+
|
|
+ /**
|
|
+ * Execute a diagnostic command
|
|
+ *
|
|
+ * @param cmd The diagnostic command to execute
|
|
+ * @return an {@link jdk.testlibrary.OutputAnalyzer} encapsulating the output of the command
|
|
+ * @throws CommandExecutorException if there is an exception on the "calling side" while trying to execute the
|
|
+ * Diagnostic Command. Exceptions thrown on the remote side are available as textual representations in
|
|
+ * stderr, regardless of the specific executor used.
|
|
+ */
|
|
+ public final OutputAnalyzer execute(String cmd) throws CommandExecutorException {
|
|
+ return execute(cmd, false);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Execute a diagnostic command
|
|
+ *
|
|
+ * @param cmd The diagnostic command to execute
|
|
+ * @param silent Do not print the command output
|
|
+ * @return an {@link jdk.testlibrary.OutputAnalyzer} encapsulating the output of the command
|
|
+ * @throws CommandExecutorException if there is an exception on the "calling side" while trying to execute the
|
|
+ * Diagnostic Command. Exceptions thrown on the remote side are available as textual representations in
|
|
+ * stderr, regardless of the specific executor used.
|
|
+ */
|
|
+ public final OutputAnalyzer execute(String cmd, boolean silent) throws CommandExecutorException {
|
|
+ if (!silent) {
|
|
+ System.out.printf("Running DCMD '%s' through '%s'%n", cmd, this.getClass().getSimpleName());
|
|
+ }
|
|
+
|
|
+ OutputAnalyzer oa = executeImpl(cmd);
|
|
+
|
|
+ if (!silent) {
|
|
+ System.out.println("---------------- stdout ----------------");
|
|
+ System.out.println(oa.getStdout());
|
|
+ System.out.println("---------------- stderr ----------------");
|
|
+ System.out.println(oa.getStderr());
|
|
+ System.out.println("----------------------------------------");
|
|
+ System.out.println();
|
|
+ }
|
|
+ return oa;
|
|
+ }
|
|
+
|
|
+ protected abstract OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException;
|
|
+}
|
|
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
|
|
new file mode 100644
|
|
index 0000000..1857a23
|
|
--- /dev/null
|
|
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
|
|
@@ -0,0 +1,36 @@
|
|
+/*
|
|
+ * Copyright (c) 2015, 2016, 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.
|
|
+ */
|
|
+
|
|
+package com.oracle.java.testlibrary;
|
|
+
|
|
+/**
|
|
+ * CommandExecutorException encapsulates exceptions thrown (on the "calling side") from the execution of Diagnostic
|
|
+ * Commands
|
|
+ */
|
|
+public class CommandExecutorException extends RuntimeException {
|
|
+ private static final long serialVersionUID = -7039597746579144280L;
|
|
+
|
|
+ public CommandExecutorException(String message, Throwable e) {
|
|
+ super(message, e);
|
|
+ }
|
|
+}
|
|
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java
|
|
new file mode 100644
|
|
index 0000000..317fc5c
|
|
--- /dev/null
|
|
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java
|
|
@@ -0,0 +1,185 @@
|
|
+/*
|
|
+ * Copyright (c) 2015, 2016, 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.
|
|
+ */
|
|
+
|
|
+package com.oracle.java.testlibrary;
|
|
+
|
|
+import javax.management.*;
|
|
+import javax.management.remote.JMXConnector;
|
|
+import javax.management.remote.JMXConnectorFactory;
|
|
+import javax.management.remote.JMXServiceURL;
|
|
+
|
|
+import java.io.IOException;
|
|
+import java.io.PrintWriter;
|
|
+import java.io.StringWriter;
|
|
+
|
|
+import java.lang.management.ManagementFactory;
|
|
+
|
|
+import java.util.HashMap;
|
|
+
|
|
+/**
|
|
+ * Executes Diagnostic Commands on the target VM (specified by a host/port combination or a full JMX Service URL) using
|
|
+ * the JMX interface. If the target is not the current VM, the JMX Remote interface must be enabled beforehand.
|
|
+ */
|
|
+public class JMXExecutor extends CommandExecutor {
|
|
+
|
|
+ private final MBeanServerConnection mbs;
|
|
+
|
|
+ /**
|
|
+ * Instantiates a new JMXExecutor targeting the current VM
|
|
+ */
|
|
+ public JMXExecutor() {
|
|
+ super();
|
|
+ mbs = ManagementFactory.getPlatformMBeanServer();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Instantiates a new JMXExecutor targeting the VM indicated by the given host/port combination or a full JMX
|
|
+ * Service URL
|
|
+ *
|
|
+ * @param target a host/port combination on the format "host:port" or a full JMX Service URL of the target VM
|
|
+ */
|
|
+ public JMXExecutor(String target) {
|
|
+ String urlStr;
|
|
+
|
|
+ if (target.matches("^\\w[\\w\\-]*(\\.[\\w\\-]+)*:\\d+$")) {
|
|
+ /* Matches "hostname:port" */
|
|
+ urlStr = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", target);
|
|
+ } else if (target.startsWith("service:")) {
|
|
+ urlStr = target;
|
|
+ } else {
|
|
+ throw new IllegalArgumentException("Could not recognize target string: " + target);
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ JMXServiceURL url = new JMXServiceURL(urlStr);
|
|
+ JMXConnector c = JMXConnectorFactory.connect(url, new HashMap<>());
|
|
+ mbs = c.getMBeanServerConnection();
|
|
+ } catch (IOException e) {
|
|
+ throw new CommandExecutorException("Could not initiate connection to target: " + target, e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException {
|
|
+ String stdout = "";
|
|
+ String stderr = "";
|
|
+
|
|
+ String[] cmdParts = cmd.split(" ", 2);
|
|
+ String operation = commandToMethodName(cmdParts[0]);
|
|
+ Object[] dcmdArgs = produceArguments(cmdParts);
|
|
+ String[] signature = {String[].class.getName()};
|
|
+
|
|
+ ObjectName beanName = getMBeanName();
|
|
+
|
|
+ try {
|
|
+ stdout = (String) mbs.invoke(beanName, operation, dcmdArgs, signature);
|
|
+ }
|
|
+
|
|
+ /* Failures on the "local" side, the one invoking the command. */
|
|
+ catch (ReflectionException e) {
|
|
+ Throwable cause = e.getCause();
|
|
+ if (cause instanceof NoSuchMethodException) {
|
|
+ /* We want JMXExecutor to match the behavior of the other CommandExecutors */
|
|
+ String message = "Unknown diagnostic command: " + operation;
|
|
+ stderr = exceptionTraceAsString(new IllegalArgumentException(message, e));
|
|
+ } else {
|
|
+ rethrowExecutorException(operation, dcmdArgs, e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Failures on the "local" side, the one invoking the command. */
|
|
+ catch (InstanceNotFoundException | IOException e) {
|
|
+ rethrowExecutorException(operation, dcmdArgs, e);
|
|
+ }
|
|
+
|
|
+ /* Failures on the remote side, the one executing the invoked command. */
|
|
+ catch (MBeanException e) {
|
|
+ stdout = exceptionTraceAsString(e);
|
|
+ }
|
|
+
|
|
+ return new OutputAnalyzer(stdout, stderr);
|
|
+ }
|
|
+
|
|
+ private void rethrowExecutorException(String operation, Object[] dcmdArgs,
|
|
+ Exception e) throws CommandExecutorException {
|
|
+ String message = String.format("Could not invoke: %s %s", operation,
|
|
+ String.join(" ", (String[]) dcmdArgs[0]));
|
|
+ throw new CommandExecutorException(message, e);
|
|
+ }
|
|
+
|
|
+ private ObjectName getMBeanName() throws CommandExecutorException {
|
|
+ String MBeanName = "com.sun.management:type=DiagnosticCommand";
|
|
+
|
|
+ try {
|
|
+ return new ObjectName(MBeanName);
|
|
+ } catch (MalformedObjectNameException e) {
|
|
+ String message = "MBean not found: " + MBeanName;
|
|
+ throw new CommandExecutorException(message, e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private Object[] produceArguments(String[] cmdParts) {
|
|
+ Object[] dcmdArgs = {new String[0]}; /* Default: No arguments */
|
|
+
|
|
+ if (cmdParts.length == 2) {
|
|
+ dcmdArgs[0] = cmdParts[1].split(" ");
|
|
+ }
|
|
+ return dcmdArgs;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Convert from diagnostic command to MBean method name
|
|
+ *
|
|
+ * Examples:
|
|
+ * help --> help
|
|
+ * VM.version --> vmVersion
|
|
+ * VM.command_line --> vmCommandLine
|
|
+ */
|
|
+ private static String commandToMethodName(String cmd) {
|
|
+ String operation = "";
|
|
+ boolean up = false; /* First letter is to be lower case */
|
|
+
|
|
+ /*
|
|
+ * If a '.' or '_' is encountered it is not copied,
|
|
+ * instead the next character will be converted to upper case
|
|
+ */
|
|
+ for (char c : cmd.toCharArray()) {
|
|
+ if (('.' == c) || ('_' == c)) {
|
|
+ up = true;
|
|
+ } else if (up) {
|
|
+ operation = operation.concat(Character.toString(c).toUpperCase());
|
|
+ up = false;
|
|
+ } else {
|
|
+ operation = operation.concat(Character.toString(c).toLowerCase());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return operation;
|
|
+ }
|
|
+
|
|
+ private static String exceptionTraceAsString(Throwable cause) {
|
|
+ StringWriter sw = new StringWriter();
|
|
+ cause.printStackTrace(new PrintWriter(sw));
|
|
+ return sw.toString();
|
|
+ }
|
|
+
|
|
+}
|
|
--
|
|
1.8.3.1
|