/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.vm.agent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import org.helios.gmx.util.JMXHelper;
import org.helios.vm.VirtualMachine;
import org.helios.vm.VirtualMachineBootstrap;
/**
* <p>Title: LocalAgentInstaller</p>
* <p>Description: Local java agent installer.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.vm.agent.LocalAgentInstaller</code></p>
*/
public class LocalAgentInstaller {
/** The created agent jar file name */
protected static final AtomicReference<String> agentJar = new AtomicReference<String>(null);
/**
* Simple example of the install commands executed.
* @param args None
*/
public static void main(String[] args) {
String fileName = createAgent();
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
VirtualMachineBootstrap.getInstance();
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(fileName);
System.out.println("Instrumentation Deployed:" + ManagementFactory.getPlatformMBeanServer().isRegistered(AgentInstrumentationMBean.AGENT_INSTR_ON));
}
/**
* Installs the local agent and returns the Instrumentation.
* @return the installed instrumentation
*/
public synchronized static Instrumentation getInstrumentation() {
return getInstrumentation(500);
}
/**
* Installs the local agent and returns the Instrumentation.
* @param timeout The time to wait for the MBean to deploy
* @return the installed instrumentation
*/
public synchronized static Instrumentation getInstrumentation(long timeout) {
Instrumentation instr = null;
try {
instr = (Instrumentation)ManagementFactory.getPlatformMBeanServer().getAttribute(AgentInstrumentationMBean.AGENT_INSTR_ON, "Instrumentation");
if(instr!=null) {
return instr;
}
} catch (Exception e) {}
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
VirtualMachineBootstrap.getInstance();
VirtualMachine vm = VirtualMachine.attach(pid);
String fileName = createAgent();
final CountDownLatch latch = new CountDownLatch(1);
final NotificationListener listener = new NotificationListener(){
@Override
public void handleNotification(Notification notification, Object handback) {
if(((MBeanServerNotification)notification).getMBeanName().equals(AgentInstrumentation.AGENT_INSTR_ON)) {
latch.countDown();
}
}
};
try {
ManagementFactory.getPlatformMBeanServer().addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, listener, new NotificationFilter(){
@Override
public boolean isNotificationEnabled(Notification notification) {
return (notification instanceof MBeanServerNotification) && notification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION);
}
}, null);
vm.loadAgent(fileName);
if(!latch.await(timeout, TimeUnit.MILLISECONDS)) {
throw new Exception("Timed out after waiting [" + timeout + "] ms. for AgentInstrumentation MBean Deployment");
}
return (Instrumentation)ManagementFactory.getPlatformMBeanServer().getAttribute(AgentInstrumentationMBean.AGENT_INSTR_ON, "Instrumentation");
} catch (Exception e) {
throw new RuntimeException("Failed to acquire instrumentation", e);
} finally {
try { ManagementFactory.getPlatformMBeanServer().removeNotificationListener(AgentInstrumentationMBean.AGENT_INSTR_ON, listener); } catch (Exception e) {}
}
}
/**
* Creates the temporary agent jar file if it has not been created
* @return The created agent file name
*/
public static String createAgent() {
if(agentJar.get()==null) {
synchronized(agentJar) {
if(agentJar.get()==null) {
FileOutputStream fos = null;
JarOutputStream jos = null;
try {
File tmpFile = File.createTempFile(LocalAgentInstaller.class.getName(), ".jar");
System.out.println("Temp File:" + tmpFile.getAbsolutePath());
tmpFile.deleteOnExit();
StringBuilder manifest = new StringBuilder();
manifest.append("Manifest-Version: 1.0\nAgent-Class: " + AgentInstrumentation.class.getName() + "\n");
manifest.append("Can-Redefine-Classes: true\n");
manifest.append("Can-Retransform-Classes: true\n");
manifest.append("Premain-Class: " + AgentInstrumentation.class.getName() + "\n");
ByteArrayInputStream bais = new ByteArrayInputStream(manifest.toString().getBytes());
Manifest mf = new Manifest(bais);
fos = new FileOutputStream(tmpFile, false);
jos = new JarOutputStream(fos, mf);
addClassesToJar(jos, AgentInstrumentation.class, AgentInstrumentationMBean.class, JMXHelper.class);
jos.flush();
jos.close();
fos.flush();
fos.close();
agentJar.set(tmpFile.getAbsolutePath());
} catch (Exception e) {
throw new RuntimeException("Failed to write Agent installer Jar", e);
} finally {
if(fos!=null) try { fos.close(); } catch (Exception e) {}
}
}
}
}
return agentJar.get();
}
/**
* Writes the passed classes to the passed JarOutputStream
* @param jos the JarOutputStream
* @param clazzes The classes to write
* @throws IOException on an IOException
*/
protected static void addClassesToJar(JarOutputStream jos, Class<?>...clazzes) throws IOException {
for(Class<?> clazz: clazzes) {
jos.putNextEntry(new ZipEntry(clazz.getName().replace('.', '/') + ".class"));
jos.write(getClassBytes(clazz));
jos.flush();
jos.closeEntry();
}
}
/**
* Returns the bytecode bytes for the passed class
* @param clazz The class to get the bytecode for
* @return a byte array of bytecode for the passed class
*/
public static byte[] getClassBytes(Class<?> clazz) {
InputStream is = null;
try {
is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class");
ByteArrayOutputStream baos = new ByteArrayOutputStream(is.available());
byte[] buffer = new byte[8092];
int bytesRead = -1;
while((bytesRead = is.read(buffer))!=-1) {
baos.write(buffer, 0, bytesRead);
}
baos.flush();
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException("Failed to read class bytes for [" + clazz.getName() + "]", e);
} finally {
if(is!=null) { try { is.close(); } catch (Exception e) {} }
}
}
}
/*
Groovy code attempting to take an exiting jar file representing byte array and adding an entry to it
without writing out to a physical file system. (Does not work)
import org.helios.gmx.classloading.ReverseClassLoader;
import java.util.jar.*;
import java.util.zip.*;
rcl = ReverseClassLoader.getInstance();
byte[] jarBytes = rcl.jarContent;
println "JAR Bytes:${jarBytes.length}";
jb4Bytes = rcl.getClass().getClassLoader().getResourceAsStream("jboss/jboss4/jboss-service.xml").getBytes();
println "JB4 Bytes:${jb4Bytes.length}";
File f = new File("/tmp/gmx.jar");
f.delete();
FileOutputStream fos = null;
ZipOutputStream jos = null;
try {
fos = new FileOutputStream(f);
jos = new ZipOutputStream(fos);
fos.write(jarBytes);
jos.putNextEntry(new ZipEntry("jboss-service.xml")); jos.write(jb4Bytes, 0, jb4Bytes.length); jos.closeEntry(); jos.flush();
fos.flush();
fos.close();
println "File Writen";
} finally {
try { jos.flush(); } catch (e) {}
try { jos.close(); } catch (e) {}
try { fos.flush(); } catch (e) {}
try { fos.close(); } catch (e) {}
}
return null;
*/