/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.olivergondza.dumpling.factory.jmx;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import com.github.olivergondza.dumpling.factory.JmxRuntimeFactory;
import com.github.olivergondza.dumpling.factory.JmxRuntimeFactory.FailedToInitializeJmxConnection;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
/**
* Wrapper around tools.jar classes to be loaded using isolated classloader.
*
* This is necessary to access those classes without tempering with system classloader.
*
* This is not part of Dumpling API.
*
* @author ogondza
*/
@SuppressWarnings("unused") // Invoked via reflection
/*package*/ final class JmxLocalProcessConnector {
private static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
// This has to be called by reflection so it can as well be private to stress this is not an API
@SuppressWarnings("unused")
private static MBeanServerConnection getServerConnection(int pid) {
try {
JMXServiceURL serviceURL = new JMXServiceURL(connectorAddress(pid));
return JMXConnectorFactory.connect(serviceURL).getMBeanServerConnection();
} catch (MalformedURLException ex) {
throw failed("JMX connection failed", ex);
} catch (IOException ex) {
throw failed("JMX connection failed", ex);
}
}
private static VirtualMachine getVm(int pid) {
try {
return VirtualMachine.attach(String.valueOf(pid));
} catch (AttachNotSupportedException ex) {
throw failed("VM does not support attach operation", ex);
} catch (IOException ex) {
throw failed("VM attach failed", ex);
}
}
private static String connectorAddress(int pid) throws IOException {
VirtualMachine vm = getVm(pid);
String address = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
if (address != null) return address;
final Properties systemProperties = vm.getSystemProperties();
List<String> diag = new ArrayList<String>(3);
try { // Java 8+, using reflection so it compiles for older releases.
Method method = VirtualMachine.class.getMethod("startLocalManagementAgent");
return (String) method.invoke(vm);
} catch (NoSuchMethodException ex) {
diag.add("VirtualMachine.startLocalManagementAgent not supported");
} catch (InvocationTargetException ex) {
throw new AssertionError(ex);
} catch (IllegalAccessException ex) {
throw new AssertionError(ex);
}
// jcmd - Hotspot && Java 7+
try {
Class<?> hsvm = Class.forName("sun.tools.attach.HotSpotVirtualMachine");
if (hsvm.isInstance(vm)) {
Method method = hsvm.getMethod("executeJCmd", String.class);
InputStream in = (InputStream) method.invoke(vm, "ManagementAgent.start_local");
in.close(); // Is there anything interesting?
address = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
if (address != null) return address;
diag.add("jcmd ManagementAgent.start_local succeeded");
}
} catch (ClassNotFoundException e) {
diag.add("not a HotSpot VM - jcmd likely unsupported");
} catch (NoSuchMethodException e) {
diag.add("HotSpot VM with no jcmd support");
} catch (InvocationTargetException e) {
throw new AssertionError(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
// If the JVM is not able to listen to JMX connections, it is necessary to have the agent loaded.
// There does not seem to be a portable way to do so. This mostly works for hotspot:
// Java 6: The management-agent.jar needs to be loaded
// Try management-agent.jar
String agentPath = systemProperties.getProperty("java.home")
+ File.separator + "lib" + File.separator
+ "management-agent.jar"
;
if (new File(agentPath).exists()) {
try {
vm.loadAgent(agentPath);
} catch (AgentLoadException ex) {
throw failed("Unable to load agent", ex);
} catch (AgentInitializationException ex) {
throw failed("Unable to initialize agent", ex);
}
address = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
if (address != null) return address;
diag.add("management-agent.jar loaded successfully");
} else {
diag.add("management-agent.jar not found");
}
// IBM JDK uses specific class hierarchy
// Note this require both JVMs are IBM ones
try {
Class<?> ibmVmClass = Class.forName("com.ibm.tools.attach.VirtualMachine");
Method attach = ibmVmClass.getMethod("attach", String.class);
Object ibmVm = attach.invoke(null, String.valueOf(pid));
Method method = ibmVm.getClass().getMethod("getSystemProperties");
method.setAccessible(true); // the class is likely package protected
Properties props = (Properties) method.invoke(ibmVm);
address = props.getProperty(CONNECTOR_ADDRESS);
if (address != null) return address;
diag.add("IBM JDK attach successful - no address provided");
} catch (ClassNotFoundException e) {
diag.add("not an IBM JDK - unable to create local JMX connection; try HOSTNAME:PORT instead");
} catch (NoSuchMethodException e) {
diag.add("IBM JDK does not seem to support attach: " + e.getMessage());
} catch (InvocationTargetException e) {
throw new AssertionError(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
throw failedUnsupported("Unable to connect to JVM: " + diag.toString(), systemProperties);
}
private static FailedToInitializeJmxConnection failed(String message, Exception ex) {
return new JmxRuntimeFactory.FailedToInitializeJmxConnection(message + ": " + ex.getMessage(), ex);
}
private static FailedToInitializeJmxConnection failedUnsupported(String message, Properties systemProperties) {
String unsupported = String.format(
"%nDumpling is talking to unsupported JVM. Report this as a bug together with following details: vendor: %s; version: %s; os: %s",
systemProperties.getProperty("java.vm.vendor"),
systemProperties.getProperty("java.version"),
systemProperties.getProperty("os.version")
);
return new JmxRuntimeFactory.FailedToInitializeJmxConnection(message + unsupported);
}
}