package com.jsonde.agent; import com.jsonde.api.Message; import com.jsonde.api.MessageListener; import com.jsonde.api.configuration.AgentConfigurationMessage; import com.jsonde.api.configuration.ClassFilterDto; import com.jsonde.profiler.Profiler; import com.jsonde.profiler.network.NetworkServerException; import com.jsonde.util.ClassUtils; import com.jsonde.util.StringUtils; import com.jsonde.util.io.IO; import com.jsonde.util.log.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.net.URL; import java.security.ProtectionDomain; public class JSondeAgent implements MessageListener, ClassFileTransformer { private final static Log log = Log.getLog(JSondeAgent.class); private static final int DEFAULT_PORT_NUMBER = 60001; private final ClassFileTransformer byteCodeTransformer; private final Profiler profiler; private final String arguments; private final Instrumentation instrumentation; private final ClassLoader resolveAgentLibrariesClassLoader; public static void premain(final String arg, Instrumentation instrumentation) { JSondeAgent jSondeAgent = new JSondeAgent(arg, instrumentation); jSondeAgent.execute(); jSondeAgent.setTransformer(); } @SuppressWarnings("unused") public static void agentmain(String arg, final Instrumentation instrumentation) { final JSondeAgent jSondeAgent = new JSondeAgent(arg, instrumentation); new Thread(new Runnable() { public void run() { jSondeAgent.execute(); jSondeAgent.redefineLoadedClasses(); jSondeAgent.setTransformer(); /*try { if (instrumentation.isRetransformClassesSupported()) { jSondeAgent.setTransformer(); Class[] classes = instrumentation.getAllLoadedClasses(); List<Class> modifiableClasses = new ArrayList<Class>(classes.length / 2); for (Class clazz : classes) { if (instrumentation.isModifiableClass(clazz)) { modifiableClasses.add(clazz); } } int classesCount = modifiableClasses.size(); int chunkSize = 100; int chunkCount = classesCount / chunkSize; for (int i = 0; i < chunkCount; i++) { Class[] classesChunk = modifiableClasses. subList(i * chunkSize, (i+1) * chunkSize). toArray(new Class[chunkSize]); instrumentation.retransformClasses(classesChunk); } Class[] classesChunk = modifiableClasses. subList(classesCount - (classesCount % chunkSize), classesCount). toArray(new Class[chunkSize]); instrumentation.retransformClasses(classesChunk); } else { jSondeAgent.redefineLoadedClasses(); jSondeAgent.setTransformer(); } } catch (NoSuchMethodError e) { jSondeAgent.redefineLoadedClasses(); jSondeAgent.setTransformer(); } catch (UnmodifiableClassException e) { e.printStackTrace(); }*/ } }).start(); } public JSondeAgent(String arguments, Instrumentation instrumentation) { System.out.println("jSonde agent started"); this.arguments = arguments; this.instrumentation = instrumentation; ResolveAgentLibrariesClassLoader resolveAgentLibrariesClassLoader = new ResolveAgentLibrariesClassLoader(); this.resolveAgentLibrariesClassLoader = resolveAgentLibrariesClassLoader; byteCodeTransformer = createByteCodeTransformer(); int portNumber = getPortNumber(); profiler = Profiler.initializeProfiler(instrumentation, portNumber); } private int getPortNumber() { try { return Integer.parseInt(arguments); } catch (NumberFormatException e) { return DEFAULT_PORT_NUMBER; } } private ClassFileTransformer createByteCodeTransformer() { try { return (ClassFileTransformer) resolveAgentLibrariesClassLoader. loadClass("com.jsonde.instrumentation.ByteCodeTransformer"). newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } public void execute() { final String METHOD_NAME = "execute()"; try { startServer(); } catch (NetworkServerException e) { log.error(METHOD_NAME, e); } } public void setTransformer() { instrumentation.addTransformer(this); } private void redefineLoadedClasses() { for (Class clazz : instrumentation.getAllLoadedClasses()) { try { redefineLoadedClass(clazz); } catch (Exception e) { System.out.println("Error while transforming class " + clazz); e.printStackTrace(); } } } private void redefineLoadedClass(Class clazz) { if (clazz.isArray()) { clazz = clazz.getComponentType(); } String className = clazz.getName(); if (shouldTransformClass(className)) { URL classFileResourceURL; ClassLoader classLoader = clazz.getClassLoader(); if (null == classLoader) { classFileResourceURL = ClassLoader.getSystemResource( ClassUtils.convertClassNameToResourceName(className)); } else { classFileResourceURL = classLoader.getResource( ClassUtils.convertClassNameToResourceName(className)); } if (null == classFileResourceURL) return; InputStream byteCodeInputStream = null; ByteArrayOutputStream originalByteArrayOutputStream = new ByteArrayOutputStream(); try { byteCodeInputStream = classFileResourceURL.openStream(); while (byteCodeInputStream.available() > 0) { originalByteArrayOutputStream.write(byteCodeInputStream.read()); } byte[] bytecode = originalByteArrayOutputStream.toByteArray(); bytecode = transform( classLoader, className, clazz, clazz.getProtectionDomain(), bytecode ); if (null != bytecode) { Profiler.getProfiler().redefineClass( bytecode, className, clazz.getClassLoader()); } } catch (IOException e) { e.printStackTrace(); } catch (IllegalClassFormatException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } finally { IO.close(originalByteArrayOutputStream); IO.close(byteCodeInputStream); } } } public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (null != agentConfigurationMessage && null != agentConfigurationMessage.getClassFilters()) { if (!shouldTransformClass(className)) { return classfileBuffer; } } Thread currentThread = Thread.currentThread(); for (Long profilerThreadId : Profiler.getProfiler().getProfilerThreadIds()) { if (currentThread.getId() == profilerThreadId) { return classfileBuffer; } } ClassLoader contextClassLoader = currentThread.getContextClassLoader(); try { currentThread.setContextClassLoader(resolveAgentLibrariesClassLoader); byte[] transformedBytes = byteCodeTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); if (null == loader || null == loader.getParent()) { String name = ClassUtils.getFullyQualifiedName(className); if (!name.startsWith("com.jsonde")) { Profiler.getProfiler().redefineClass( transformedBytes, name, loader); } return classfileBuffer; } else { return transformedBytes; } } finally { currentThread.setContextClassLoader(contextClassLoader); } } private boolean shouldTransformClass(String className) { if ((className.startsWith("com.jsonde")) && (!className.startsWith("com.jsonde.instrumentation.samples"))) return false; boolean transform = true; for (ClassFilterDto classFilter : agentConfigurationMessage.getClassFilters()) { String regex = StringUtils.wildcardToRegex(classFilter.getPackageName()); boolean matches = ClassUtils.getFullyQualifiedName(className).matches(regex); if (matches) { transform = classFilter.isInclusive(); } } return transform; } public synchronized void startServer() throws NetworkServerException { final String METHOD_NAME = "startServer()"; profiler.addMessageListener(this); profiler.start(); while (null == agentConfigurationMessage) { try { wait(); } catch (InterruptedException e) { log.error(METHOD_NAME, e); Thread.currentThread().interrupt(); } } profiler.removeMessageListener(this); } private volatile AgentConfigurationMessage agentConfigurationMessage; public synchronized void onMessage(Message message) { final String METHOD_NAME = "onMessage(Message)"; if (log.isTraceEnabled()) { log.trace(METHOD_NAME, "Recieved Message" + message); } if (message instanceof AgentConfigurationMessage) { agentConfigurationMessage = (AgentConfigurationMessage) message; notifyAll(); } } }