package com.jsonde.profiler; import com.jsonde.api.Message; import com.jsonde.api.MessageListener; import com.jsonde.api.function.FunctionRequest; import com.jsonde.api.function.FunctionResponse; import com.jsonde.api.function.echo.EchoFunctionRequest; import com.jsonde.api.function.echo.EchoFunctionResponse; import com.jsonde.api.function.heap.ClassHeapDataDto; import com.jsonde.api.function.heap.DumpHeapFunctionRequest; import com.jsonde.api.function.heap.DumpHeapFunctionResponse; import com.jsonde.api.methodCall.*; import com.jsonde.profiler.heap.ClassHeapData; import com.jsonde.profiler.heap.HeapAnalyzer; import com.jsonde.profiler.network.NetworkServer; import com.jsonde.profiler.network.NetworkServerException; import com.jsonde.profiler.network.NetworkServerImpl; import com.jsonde.profiler.telemetry.TelemetryDataProvider; import com.jsonde.util.ClassUtils; import com.jsonde.util.ObjectIdGenerator; import com.jsonde.util.ObjectIsAbsentException; import com.jsonde.util.log.Log; import java.lang.instrument.ClassDefinition; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class ProfilerImpl extends Profiler implements MessageListener { private static final Log log = Log.getLog(ProfilerImpl.class); private Set<ThreadLocalProfiler> threadLocalProfilers = Collections.synchronizedSet( new HashSet<ThreadLocalProfiler>() ); private final ThreadLocal<ThreadLocalProfiler> threadLocalProfiler = new ThreadLocal<ThreadLocalProfiler>() { @Override protected synchronized ThreadLocalProfiler initialValue() { ThreadLocalProfiler threadLocalProfiler = new ThreadLocalProfiler(ProfilerImpl.this); threadLocalProfilers.add(threadLocalProfiler); return threadLocalProfiler; } }; private final Instrumentation instrumentation; private final AtomicLong methodCallIdGenerator = new AtomicLong(); private final AtomicLong methodIdGenerator = new AtomicLong(); private final ObjectIdGenerator<ClassLoader> classLoaderIdGenerator = new ObjectIdGenerator<ClassLoader>(); private final ObjectIdGenerator<ObjectIdGenerator.Pair<String, ClassLoader>> classIdGenerator = new ObjectIdGenerator<ObjectIdGenerator.Pair<String, ClassLoader>>(); private NetworkServer networkServer; private HeapAnalyzer heapAnalyzer; private DaemonThreadFactory daemonThreadFactory = new DaemonThreadFactory(); private Executor redefineClassesExecutor = Executors.newSingleThreadExecutor(daemonThreadFactory); private Executor invokeFunctionExecutor = Executors.newCachedThreadPool(daemonThreadFactory); private ScheduledExecutorService telemetryExecutor = Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); private Set<Class> redefinedClasses = new HashSet<Class>(); public ProfilerImpl(Instrumentation instrumentation, int port) { this.instrumentation = instrumentation; networkServer = new NetworkServerImpl(port, daemonThreadFactory); heapAnalyzer = new HeapAnalyzer(daemonThreadFactory); networkServer.addMessageListener(this); } public ProfilerImpl() { instrumentation = null; } protected NetworkServer getServer() { return networkServer; } protected void setServer(NetworkServer networkServer) { this.networkServer = networkServer; } public void onMessage(Message message) { if (message instanceof FunctionRequest) { final FunctionRequest functionRequest = (FunctionRequest) message; invokeFunctionExecutor.execute(new Runnable() { public void run() { FunctionResponse functionResponse = invokeFunction(functionRequest); if (null != functionResponse) { networkServer.sendMessage(functionResponse); } } }); for (ThreadLocalProfiler tlp : threadLocalProfilers) { tlp.dump(); } } } private FunctionResponse invokeFunction(FunctionRequest functionRequest) { if (functionRequest instanceof EchoFunctionRequest) { return new EchoFunctionResponse((EchoFunctionRequest) functionRequest); } else if (functionRequest instanceof DumpHeapFunctionRequest) { DumpHeapFunctionResponse functionResponse = new DumpHeapFunctionResponse(functionRequest); for (Map.Entry<Long, ClassHeapData> mapEntry : heapAnalyzer.getHeapData().entrySet()) { functionResponse.addClassHeapDataDto(new ClassHeapDataDto( mapEntry.getKey(), mapEntry.getValue().getCreateCounter(), mapEntry.getValue().getCollectCounter(), mapEntry.getValue().getTotalCurrentSize() )); } return functionResponse; } return null; } @Override protected void enterMethodImpl(long methodId, Object object, Object[] arguments) { threadLocalProfiler.get().enterMethodImpl(methodId, object, arguments); } @Override protected void enterConstructorImpl(long methodId, Object object, Object[] arguments) { threadLocalProfiler.get().enterConstructorImpl(methodId, object, arguments); long objectSize = instrumentation.getObjectSize(object); heapAnalyzer.createObject(object, methodId, objectSize); } @Override protected void preEnterConstructorImpl(long methodId) { threadLocalProfiler.get().preEnterConstructorImpl(methodId); } @Override protected void leaveMethodImpl(boolean isVoid, boolean isThrowsException, Object result) { threadLocalProfiler.get().leaveMethodImpl(isVoid, isThrowsException, result); } @Override public long registerMethod( final long classId, final int access, final String name, final String desc, final String signature, final String[] exceptions) { final String METHOD_NAME = "registerMethod"; try { if (log.isTraceEnabled()) { log.entering( METHOD_NAME, classId, access, name, desc, signature, Arrays.toString(exceptions) ); } final long methodId = methodIdGenerator.getAndIncrement(); RegisterMethodMessage registerMethodMessage = new RegisterMethodMessage( methodId, classId, access, name, desc, signature, exceptions ); sendMessage(registerMethodMessage); log.exiting(METHOD_NAME, methodId); return methodId; } catch (Throwable e) { log.error(METHOD_NAME, e); return UNDEFINED_METHOD_ID; } finally { log.exiting(METHOD_NAME); } } public void sendMessage(Message registerMethodMessage) { networkServer.sendMessage(registerMethodMessage); } @Override public long registerClass( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces, final ClassLoader classLoader) { final String METHOD_NAME = "registerClass"; try { if (log.isTraceEnabled()) { log.entering( METHOD_NAME, version, access, name, signature, superName, Arrays.toString(interfaces), classLoader ); } String className = ClassUtils.getFullyQualifiedName(name); final long classId = generateClassId(classLoader, className); RegisterClassMessage registerClassMessage = new RegisterClassMessage( classId, version, access, className, signature, superName, interfaces ); networkServer.sendMessage(registerClassMessage); log.exiting(METHOD_NAME, classId); return classId; } catch (Throwable e) { e.printStackTrace(); log.error(METHOD_NAME, e); return UNDEFINED_CLASS_ID; } finally { log.exiting(METHOD_NAME); } } public long generateClassId(ClassLoader classLoader, String className) { return classIdGenerator.getId(ObjectIdGenerator.pair(className, classLoader)); } public long generateClassIdAndRegisterIfAbsent(Class clazz) { ClassLoader classLoader = clazz.getClassLoader(); String className = clazz.getName(); try { return classIdGenerator.pollId(ObjectIdGenerator.pair(className, classLoader)); } catch (ObjectIsAbsentException e) { long classId = generateClassId(classLoader, className); RegisterClassMessage registerClassMessage = new RegisterClassMessage(classId, className); networkServer.sendMessage(registerClassMessage); describeClassImpl(classId, clazz); return classId; } } @Override protected void describeClassImpl(long classId, Class clazz) { DescribeClassMessage describeClassMessage = new DescribeClassMessage(classId, false); describeClassMessage.setClassLoaderId( classLoaderIdGenerator.getId(clazz.getClassLoader()) ); ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (null != protectionDomain) { CodeSource codeSource = protectionDomain.getCodeSource(); if (null != codeSource) { URL codeLocation = codeSource.getLocation(); describeClassMessage.setCodeLocation(codeLocation.toExternalForm()); } } networkServer.sendMessage(describeClassMessage); } @Override public void describeRedefinableClass(long classId, Class clazz) { DescribeClassMessage describeClassMessage = new DescribeClassMessage(classId, true); describeClassMessage.setClassLoaderId( classLoaderIdGenerator.getId(clazz.getClassLoader()) ); ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (null != protectionDomain) { CodeSource codeSource = protectionDomain.getCodeSource(); if (null != codeSource) { URL codeLocation = codeSource.getLocation(); describeClassMessage.setCodeLocation(codeLocation.toExternalForm()); } } networkServer.sendMessage(describeClassMessage); } @Override protected void processMethodCall( List<MethodCallDto> methodCallDtos, MethodCallSummaryDto methodCallSummaryDto, boolean complete) { MethodCallMessage methodCallMessage = new MethodCallMessage( methodCallDtos, methodCallSummaryDto, complete); networkServer.sendMessage(methodCallMessage); } @Override public void startServer() throws NetworkServerException { networkServer.start(); heapAnalyzer.start(); TelemetryDataProvider telemetryDataProvider = new TelemetryDataProvider(this); telemetryExecutor.scheduleWithFixedDelay(telemetryDataProvider, 0, 1, TimeUnit.SECONDS); } @Override public void stopServer() throws NetworkServerException { networkServer.stop(); heapAnalyzer.stop(); telemetryExecutor.shutdown(); try { telemetryExecutor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("stopServer", e); throw new NetworkServerException(e); } } public void addMessageListener(MessageListener messageListener) { networkServer.addMessageListener(messageListener); } public void removeMessageListener(MessageListener messageListener) { networkServer.removeMessageListener(messageListener); } public void redefineClass( final byte[] bytecode, final String className, final ClassLoader classLoader) { redefineClassesExecutor.execute(new Runnable() { public void run() { try { Class clazz; if (null == classLoader) { clazz = ClassLoader.getSystemClassLoader().loadClass(className); } else { clazz = classLoader.loadClass(className); } if (redefinedClasses.contains(clazz)) return; redefinedClasses.add(clazz); ClassDefinition classDefinition = new ClassDefinition(clazz, bytecode); instrumentation.redefineClasses(new ClassDefinition[]{classDefinition}); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (UnmodifiableClassException e) { e.printStackTrace(); } } }); } @Override public Collection<Long> getProfilerThreadIds() { ThreadGroup threadGroup = daemonThreadFactory.getThreadGroup(); Thread[] threads = new Thread[threadGroup.activeCount()]; threadGroup.enumerate(threads); Collection<Long> profilerThreadIds = new HashSet<Long>(); for (Thread thread : threads) { profilerThreadIds.add(thread.getId()); } return profilerThreadIds; } @Override public long generateMethodCallId() { return methodCallIdGenerator.getAndIncrement(); } }