/*
* RHQ Management Platform
* Copyright (C) 2011-2012 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jmx.util;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.management.remote.JMXServiceURL;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.sigar.ProcCred;
import org.hyperic.sigar.SigarException;
import org.hyperic.sigar.SigarProxy;
import org.rhq.core.system.ProcessInfo;
import org.rhq.core.system.SigarAccess;
import org.rhq.core.system.SystemInfoException;
import org.rhq.plugins.jmx.MBeanResourceComponent;
/**
* @author Ian Springer
*/
public class JvmUtility {
private static final Log LOG = LogFactory.getLog(MBeanResourceComponent.class);
private static final String AGENT_PROP_JMXREMOTE_LOCAL_CONNECTOR_ADDRESS =
"com.sun.management.jmxremote.localConnectorAddress";
private static boolean attachApiAvailable;
static {
try {
Class.forName("com.sun.tools.attach.VirtualMachine");
attachApiAvailable = true;
} catch (ClassNotFoundException e) {
LOG.warn("JDK tools.jar not found on system classpath - cannot discover JVMs using Sun JVM Attach API; "
+ "to fix this, run the RHQ Agent on a JDK, rather than a JRE.");
}
}
/**
* TODO
*
* @param process a java process
* @return the JMX service URL that can be used to obtain the java process' MBeanServer, or null if connecting to
* the process is not possible
*/
public static JMXServiceURL extractJMXServiceURL(ProcessInfo process) {
if (!attachApiAvailable) {
LOG.debug("Returning null, since the Attach API is not available...");
return null;
}
JMXServiceURL url;
try {
VirtualMachine vm = attachToVirtualMachine(process);
if (vm == null) {
return null;
}
String jmxConnectorAddress = getJmxConnectorAddress(vm);
try {
vm.detach();
} catch (Exception e) {
// We already succeeded in obtaining the connector address, so just log this, rather than throwing an exception.
LOG.error("Failed to detach from JVM [" + vm + "].", e);
}
url = new JMXServiceURL(jmxConnectorAddress);
} catch (Exception e) {
throw new RuntimeException("Failed to extract JMX service URL for process with PID [" + process.getPid()
+ "].", e);
}
LOG.debug("JMX service URL for java process with PID [" + process.getPid() + "]: " + url);
return url;
}
private static VirtualMachine attachToVirtualMachine(ProcessInfo process) throws AttachNotSupportedException,
IOException {
VirtualMachine vm = null;
List<VirtualMachineDescriptor> vmDescriptors = VirtualMachine.list();
for (VirtualMachineDescriptor vmDescriptor : vmDescriptors) {
if (Long.valueOf(vmDescriptor.id()) == process.getPid()) {
boolean attachPossible = false;
String vmUserName = process.getCredentialsName().getUser();
String agentUserName = System.getProperty("user.name");
if(vmUserName == null || agentUserName == null) {
// By default this would make more sense, but lets not break any existing behavior
long targetEuid = process.freshSnapshot().getCredentials().getEuid();
long agentEuid = getAgentProcessUid();
if(agentEuid > 0 && targetEuid == agentEuid) {
attachPossible = true;
}
} else {
attachPossible = agentUserName.equals(vmUserName);
}
if (attachPossible) {
LOG.debug("Attaching to JVM for java process with PID [" + process.getPid() + "]...");
vm = VirtualMachine.attach(vmDescriptor);
LOG.debug("Attached to JVM [" + vm + "].");
} else {
LOG.debug("Cannot attach to JVM for java process with PID [" + process.getPid()
+ "], because it is running as a different user (" + vmUserName
+ ") than the user the Agent is running as (" + agentUserName + ").");
}
break;
}
}
return vm;
}
private static long getAgentProcessUid() {
try {
SigarProxy sigar = SigarAccess.getSigar();
ProcCred procCred = sigar.getProcCred(sigar.getPid());
return procCred.getEuid();
} catch (SystemInfoException e) {
return 0;
} catch (SigarException e) {
return 0;
}
}
private static String getJmxConnectorAddress(VirtualMachine vm) throws IOException {
String jmxConnectorAddress = vm.getAgentProperties().getProperty(AGENT_PROP_JMXREMOTE_LOCAL_CONNECTOR_ADDRESS);
LOG.debug("Connector address for JVM [" + vm + "] is [" + jmxConnectorAddress + "].");
if (jmxConnectorAddress == null) {
// java.home always points to the jre dir (e.g. /usr/java/default/jre).
String jreDir = vm.getSystemProperties().getProperty("java.home");
// management-agent.jar is included with the v6 JRE, so we can rely on it always being there.
File jmxAgentJarFile = new File(jreDir, "lib/management-agent.jar");
String jmxAgentJar = jmxAgentJarFile.getCanonicalPath();
try {
vm.loadAgent(jmxAgentJar);
} catch (Exception e) {
throw new RuntimeException("Failed to load JVM agent from [" + jmxAgentJar + "].", e);
}
// JMX agent is started - get the connector address.
LOG.debug("JMX agent started - getting the connector address...");
jmxConnectorAddress = vm.getAgentProperties().getProperty(AGENT_PROP_JMXREMOTE_LOCAL_CONNECTOR_ADDRESS);
if (jmxConnectorAddress == null) {
throw new RuntimeException("Failed to determine JMX connector address.");
}
}
return jmxConnectorAddress;
}
private JvmUtility() {
}
}