/*
* Copyright (C) 2014 Mark Clarke
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package za.co.jumpingbean.gc.service;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.File;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.RuntimeMXBean;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Set;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import za.co.jumpingbean.gc.service.constants.EdenSpace;
import za.co.jumpingbean.gc.service.constants.OldGenerationCollector;
import za.co.jumpingbean.gc.service.constants.OldGenerationSpace;
import za.co.jumpingbean.gc.service.constants.PermGen;
import za.co.jumpingbean.gc.service.constants.SurvivorSpace;
import za.co.jumpingbean.gc.service.constants.YoungGenerationCollector;
import za.co.jumpingbean.gc.testApp.jmx.GCGeneratorMBean;
/**
*
* @author mark
*/
public class JMXQueryRunner {
private final MBeanServerConnection server;
private GarbageCollectorMXBean oldGenCollector;
private GarbageCollectorMXBean youngGenCollector;
private MemoryPoolMXBean edenSpace;
private MemoryPoolMXBean survivorSpace;
private MemoryPoolMXBean permGenSpace;
private MemoryPoolMXBean oldGenSpace;
private GCGeneratorMBean garbageGenerator;
private RuntimeMXBean runtime;
private JMXQueryRunner(String port) throws IOException {
HashMap<String, String> map = new HashMap<>();
map.put("jmx.remote.x.request.waiting.timeout", "5000");
JMXConnector conn = null;
try {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi:"
+ "//127.0.0.1:" + port + "/jmxrmi");
conn = JMXConnectorFactory.connect(url, map);
server = conn.getMBeanServerConnection();
} catch (IOException ex) {
if (conn != null) {
conn.close();
}
//server = null;
throw new IOException("Time out connecting to JMX port " + port);
}
}
private JMXQueryRunner(JMXServiceURL url,String username,String password) throws IOException {
HashMap<String, String[]> map = new HashMap<>();
String[] timeout = {"5000"};
map.put("jmx.remote.x.request.waiting.timeout", timeout);
String[] credentials = {username,password};
map.put(JMXConnector.CREDENTIALS, credentials);
JMXConnector conn = null;
try {
conn = JMXConnectorFactory.connect(url, map);
server = conn.getMBeanServerConnection();
} catch (IOException ex) {
if (conn != null) {
conn.close();
}
//server = null;
throw new IOException("Time out connecting to JMX port");
}
}
/**
* This method does not pass any test. The undocumented ConnectorAddressLink
* method always returns null. There appears to be no reliable way to get
* the jmx address of processes running on the box unless the address is
* already known.
*
* @param pid
* @throws IOException
*/
private JMXQueryRunner(int pid) throws IOException {
JMXConnector conn = null;
try {
VirtualMachine vm = VirtualMachine.attach(Integer.toString(pid));
String connectorAddr = vm.getAgentProperties().getProperty(
"com.sun.management.jmxremote.localConnectorAddress");
if (connectorAddr == null) {
String agent = vm.getSystemProperties().getProperty(
"java.home") + File.separator + "lib" + File.separator
+ "management-agent.jar";
vm.loadAgent(agent);
connectorAddr = vm.getAgentProperties().getProperty(
"com.sun.management.jmxremote.localConnectorAddress");
}
JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);
conn = JMXConnectorFactory.connect(serviceURL);
server = conn.getMBeanServerConnection();
} catch (AttachNotSupportedException | AgentLoadException | AgentInitializationException ex) {
if (conn != null) {
conn.close();
}
throw new IOException("Could not connect to JMX agent or it is not supported");
}
}
public static JMXQueryRunner createJMXQueryRunner(int pid) throws IOException {
JMXQueryRunner qry = new JMXQueryRunner(pid);
qry.init();
return qry;
}
public static JMXQueryRunner createJMXQueryRunner(JMXServiceURL url,String username,
String password) throws
IOException {
JMXQueryRunner qry = new JMXQueryRunner(url,username,password);
qry.init();
return qry;
}
public static JMXQueryRunner createJMXQueryRunner(String port) throws
IOException {
JMXQueryRunner qry = new JMXQueryRunner(port);
qry.init();
return qry;
}
/**
* Must be called after JMXConnection creation to initialise MBeans
*/
public void init() {
try {
this.getCollectors();
this.getMemoryPools();
this.getGCGeneratorBean();
this.getRuntimeBean();
} catch (IOException | MalformedObjectNameException ex) {
throw new IllegalStateException("error initialising gc and memory pool mbeans");
}
}
private void getCollectors() throws MalformedObjectNameException,
IOException, IllegalStateException {
Set<ObjectName> gcNames = server.queryNames(new ObjectName(
ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE
+ ",name=*"), null);
for (ObjectName objName : gcNames) {
GarbageCollectorMXBean bean = (ManagementFactory.
newPlatformMXBeanProxy(server, objName.toString(),
GarbageCollectorMXBean.class));
if (OldGenerationCollector.isMember(bean.getName())) {
oldGenCollector = bean;
} else if (YoungGenerationCollector.isMember(bean.getName())) {
youngGenCollector = bean;
} else {
throw new IllegalStateException("Collector not found");
}
}
}
private void getMemoryPools() throws MalformedObjectNameException,
IOException, IllegalStateException {
Set<ObjectName> memPoolNames = server.queryNames(new ObjectName(
ManagementFactory.MEMORY_POOL_MXBEAN_DOMAIN_TYPE
+ ",name=*"), null);
for (ObjectName objName : memPoolNames) {
MemoryPoolMXBean bean = ManagementFactory.newPlatformMXBeanProxy(server,
objName.toString(), MemoryPoolMXBean.class);
if (EdenSpace.isMember(bean.getName())) {
this.edenSpace = bean;
} else if (SurvivorSpace.isMember(bean.getName())) {
this.survivorSpace = bean;
} else if (PermGen.isMember(bean.getName())) {
this.permGenSpace = bean;
} else if (OldGenerationSpace.isMember(bean.getName())) {
this.oldGenSpace = bean;
} else if (bean.getName().equals("Compressed Class Space")
|| bean.getName().equals("Code Cache")) {
//Not mapped yet, from G1 collector
} else {
throw new IllegalStateException("Memory pool not found " + bean.getName());
}
}
}
private void getGCGeneratorBean() throws IOException, MalformedObjectNameException {
ObjectName objName = new ObjectName("JumpingBean:name=GCGenerator");
garbageGenerator = JMX.newMBeanProxy(server, objName, GCGeneratorMBean.class);
}
public GarbageCollectorMXBean getOldGenCollector() {
return oldGenCollector;
}
public GarbageCollectorMXBean getYoungGenCollector() {
return youngGenCollector;
}
public MemoryPoolMXBean getEdenSpace() {
return edenSpace;
}
public MemoryPoolMXBean getSurvivorSpace() {
return survivorSpace;
}
public MemoryPoolMXBean getPermGenSpace() {
return permGenSpace;
}
public MemoryPoolMXBean getOldGenSpace() {
return oldGenSpace;
}
public GCGeneratorMBean getGCGenerator() {
return this.garbageGenerator;
}
public String getGCInfo() {
DecimalFormat df = new DecimalFormat("#,###,##0.000");
StringBuilder str = new StringBuilder("Young GCs: Count ");
str.append(youngGenCollector.getCollectionCount()).append(" ");
str.append("Time ").append(df.format((double) youngGenCollector.getCollectionTime() / 1000d));
str.append(" | ");
str.append("Old Gen GCs: Count").append(oldGenCollector.getCollectionCount()).append(" ");
str.append("Time ").append(df.format((double) oldGenCollector.getCollectionTime() / 1000d));
return str.toString();
}
String getJavaVersion() {
return runtime.getSpecVersion();
}
private void getRuntimeBean()
throws MalformedObjectNameException,
IOException, IllegalStateException {
Set<ObjectName> runtimeNames = server.queryNames(new ObjectName(
ManagementFactory.RUNTIME_MXBEAN_NAME), null);
RuntimeMXBean bean = (ManagementFactory.
newPlatformMXBeanProxy(server, runtimeNames.iterator().
next().toString(),
RuntimeMXBean.class));
this.runtime = bean;
}
}