/* * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.btrace; import com.sun.btrace.instr.RunnableGenerator; import java.lang.management.ManagementFactory; import java.lang.instrument.Instrumentation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.btrace.aggregation.Aggregation; import com.sun.btrace.aggregation.AggregationKey; import com.sun.btrace.aggregation.AggregationFunction; import com.sun.btrace.annotations.OnError; import com.sun.btrace.annotations.OnExit; import com.sun.btrace.annotations.OnTimer; import com.sun.btrace.annotations.OnEvent; import com.sun.btrace.annotations.OnLowMemory; import com.sun.btrace.comm.Command; import com.sun.btrace.comm.ErrorCommand; import com.sun.btrace.comm.EventCommand; import com.sun.btrace.comm.ExitCommand; import com.sun.btrace.comm.MessageCommand; import com.sun.btrace.comm.NumberDataCommand; import com.sun.btrace.comm.NumberMapDataCommand; import com.sun.btrace.comm.StringMapDataCommand; import com.sun.btrace.comm.GridDataCommand; import com.sun.btrace.org.jctools.queues.MessagePassingQueue; import com.sun.btrace.org.jctools.queues.MpmcArrayQueue; import com.sun.btrace.org.jctools.queues.MpscChunkedArrayQueue; import com.sun.btrace.profiling.MethodInvocationProfiler; import java.lang.management.GarbageCollectorMXBean; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.lang.management.LockInfo; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryNotificationInfo; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.lang.management.MonitorInfo; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import javax.management.ListenerNotFoundException; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import java.lang.management.OperatingSystemMXBean; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Perf; import sun.misc.Unsafe; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import sun.security.action.GetPropertyAction; /** * Helper class used by BTrace built-in functions and * also acts runtime "manager" for a specific BTrace client * and sends Commands to the CommandListener passed. * * @author A. Sundararajan * @author Christian Glencross (aggregation support) * @author Joachim Skeie (GC MBean support, advanced Deque manipulation) * @author KLynch */ public final class BTraceRuntime { private static final class RTWrapper { private BTraceRuntime rt = null; boolean set(BTraceRuntime other) { if (rt != null && other != null) { return false; } rt = other; return true; } void escape(Callable<Void> c) { BTraceRuntime oldRuntime = rt; rt = null; try { c.call(); } catch (Exception ignored) { } finally { if (oldRuntime != null) { rt = oldRuntime; } } } } private static final class ConsumerWrapper implements MessagePassingQueue.Consumer<Command> { private final CommandListener cmdHandler; private final AtomicBoolean exitSignal; public ConsumerWrapper(CommandListener cmdHandler, AtomicBoolean exitSignal) { this.cmdHandler = cmdHandler; this.exitSignal = exitSignal; } @Override public void accept(Command t) { try { cmdHandler.onCommand(t); } catch (IOException e) { e.printStackTrace(System.err); } if (t.getType() == Command.EXIT) { exitSignal.set(true); } } } // we need Unsafe to load BTrace class bytes as // bootstrap class private static volatile Unsafe unsafe = null; private static Properties dotWriterProps; // a dummy BTraceRuntime instance private static BTraceRuntime dummy; // are we running with DTrace support enabled? private static boolean dtraceEnabled; private static final boolean messageTimestamp = false; private static final String LINE_SEPARATOR; private static boolean isNewerThan8 = false; // the command FIFO queue related settings private static final int CMD_QUEUE_LIMIT_DEFAULT = 100; public static final String CMD_QUEUE_LIMIT_KEY = "com.sun.btrace.runtime.cmdQueueLimit"; // the command FIFO queue upper limit private static int CMD_QUEUE_LIMIT; static { setupCmdQueueParams(); try { Reflection.class.getMethod("getCallerClass"); isNewerThan8 = true; } catch (NoSuchMethodException | SecurityException ex) { // ignore } // ignore dummy = new BTraceRuntime(); LINE_SEPARATOR = System.getProperty("line.separator"); } private static ThreadLocal<RTWrapper> rt = new ThreadLocal<RTWrapper>() { @Override protected RTWrapper initialValue() { return new RTWrapper(); } }; // for testing purposes private static boolean uniqueClientClassNames = true; // BTraceRuntime against BTrace class name private static Map<String, BTraceRuntime> runtimes = new ConcurrentHashMap<>(); // a set of all the client names connected so far private static Set<String> clients = new HashSet<>(); // jvmstat related stuff // to read and write perf counters private static volatile Perf perf; // interface to read perf counters of this process private static volatile PerfReader perfReader; // performance counters created by this client private static Map<String, ByteBuffer> counters = new HashMap<>(); // Few MBeans used to implement certain built-in functions private static volatile HotSpotDiagnosticMXBean hotspotMBean; private static volatile MemoryMXBean memoryMBean; private static volatile RuntimeMXBean runtimeMBean; private static volatile ThreadMXBean threadMBean; private static volatile List<GarbageCollectorMXBean> gcBeanList; private static volatile List<MemoryPoolMXBean> memPoolList; private static volatile OperatingSystemMXBean operatingSystemMXBean; // bytecode generator that generates Runnable implementations private static RunnableGenerator runnableGenerator; // Per-client state starts here. private final DebugSupport debug; // current thread's exception private ThreadLocal<Throwable> currentException = new ThreadLocal<>(); // "command line" args supplied by client private final String[] args; // whether current runtime has been disabled? private volatile boolean disabled; // Class object of the BTrace class [of this client] private final String className; // BTrace Class object corresponding to this client private Class clazz; // instrumentation level field for each runtime private Field level; // does the client have exit action? private Method exitHandler; // does the client have exception handler action? private Method exceptionHandler; // array of timer callback methods private Method[] timerHandlers; // map of client event handling methods private Map<String, Method> eventHandlers; // low memory handlers private Map<String, Method> lowMemHandlers; // timer to run profile provider actions private volatile Timer timer; // executer to run low memory handlers private volatile ExecutorService threadPool; // Memory MBean listener private volatile NotificationListener memoryListener; // Command queue for the client private final MpscChunkedArrayQueue<Command> queue; private static class SpeculativeQueueManager { // maximum number of speculative buffers private static final int MAX_SPECULATIVE_BUFFERS = Short.MAX_VALUE; // per buffer message limit private static final int MAX_SPECULATIVE_MSG_LIMIT = Short.MAX_VALUE; // next speculative buffer id private int nextSpeculationId; // speculative buffers map private ConcurrentHashMap<Integer, MpmcArrayQueue<Command>> speculativeQueues; // per thread current speculative buffer id private ThreadLocal<Integer> currentSpeculationId; SpeculativeQueueManager() { speculativeQueues = new ConcurrentHashMap<>(); currentSpeculationId = new ThreadLocal<>(); } void clear() { speculativeQueues.clear(); speculativeQueues = null; currentSpeculationId.remove(); currentSpeculationId = null; } int speculation() { int nextId = getNextSpeculationId(); if (nextId != -1) { speculativeQueues.put(nextId, new MpmcArrayQueue<Command>(MAX_SPECULATIVE_MSG_LIMIT)); } return nextId; } boolean send(Command cmd) { if (currentSpeculationId != null){ Integer curId = currentSpeculationId.get(); if ((curId != null) && (cmd.getType() != Command.EXIT)) { MpmcArrayQueue<Command> sb = speculativeQueues.get(curId); if (sb != null) { if (!sb.offer(cmd)) { sb.clear(); sb.offer(new MessageCommand("speculative buffer overflow: " + curId)); } return true; } } } return false; } void speculate(int id) { validateId(id); currentSpeculationId.set(id); } void commit(int id, MpscChunkedArrayQueue<Command> result) { validateId(id); currentSpeculationId.set(null); final MpmcArrayQueue<Command> sb = speculativeQueues.get(id); if (sb != null) { result.addAll(sb); sb.clear(); } } void discard(int id) { validateId(id); currentSpeculationId.set(null); speculativeQueues.get(id).clear(); } // -- Internals only below this point private synchronized int getNextSpeculationId() { if (nextSpeculationId == MAX_SPECULATIVE_BUFFERS) { return -1; } return nextSpeculationId++; } private void validateId(int id) { if (! speculativeQueues.containsKey(id)) { throw new RuntimeException("invalid speculative buffer id: " + id); } } } // per client speculative buffer manager private final SpeculativeQueueManager specQueueManager; // background thread that sends Commands to the handler private volatile Thread cmdThread; private final Instrumentation instrumentation; private final AtomicBoolean exitting = new AtomicBoolean(false); private final MessagePassingQueue.WaitStrategy waitStrategy = new MessagePassingQueue.WaitStrategy() { @Override public int idle(int i) { if (exitting.get()) return 0; try { if (i < 3000) { Thread.yield(); } else if (i < 3100) { Thread.sleep(1); } else { Thread.sleep(500); } } catch (InterruptedException e) { } return i+1; } }; private final MessagePassingQueue.ExitCondition exitCondition = new MessagePassingQueue.ExitCondition() { @Override public boolean keepRunning() { return !exitting.get(); } }; private BTraceRuntime() { debug = new DebugSupport(null); args = null; queue = null; specQueueManager = null; className = null; instrumentation = null; } public BTraceRuntime(final String className, String[] args, final CommandListener cmdListener, DebugSupport ds, Instrumentation inst) { this.args = args; this.queue = new MpscChunkedArrayQueue<>(CMD_QUEUE_LIMIT_DEFAULT); this.specQueueManager = new SpeculativeQueueManager(); this.className = className; this.instrumentation = inst; this.debug = ds != null ? ds : new DebugSupport(null); runtimes.put(className, this); this.cmdThread = new Thread(new Runnable() { @Override public void run() { try { BTraceRuntime.enter(); queue.drain( new ConsumerWrapper(cmdListener, exitting), waitStrategy, exitCondition ); } finally { runtimes.remove(className); queue.clear(); specQueueManager.clear(); BTraceRuntime.leave(); disabled = true; } } }); cmdThread.setDaemon(true); cmdThread.start(); } public static void initUnsafe() { if (unsafe == null) { unsafe = Unsafe.getUnsafe(); } } static int getInstrumentationLevel() { BTraceRuntime cur = getCurrent(); try { return cur.level.getInt(cur); } catch (Exception e) { return 0; } } static void setInstrumentationLevel(int level) { BTraceRuntime cur = getCurrent(); try { cur.level.set(cur, level); } catch (Exception e) { // ignore } } public static String getClientName(String forClassName) { if (!uniqueClientClassNames) { return forClassName; } String name = forClassName; int suffix = 1; while (clients.contains(name)) { name = forClassName + "$" + (suffix++); } clients.add(name); return name; } @CallerSensitive public static void init(PerfReader perfRead, RunnableGenerator runGen) { initUnsafe(); Class caller = isNewerThan8 ? Reflection.getCallerClass() : Reflection.getCallerClass(2); if (! caller.getName().equals("com.sun.btrace.agent.Client")) { throw new SecurityException("unsafe init"); } perfReader = perfRead; runnableGenerator = runGen; loadLibrary(perfRead.getClass().getClassLoader()); } @CallerSensitive public Class defineClass(byte[] code) { Class caller = isNewerThan8 ? Reflection.getCallerClass() : Reflection.getCallerClass(2); if (! caller.getName().startsWith("com.sun.btrace.")) { throw new SecurityException("unsafe defineClass"); } return defineClassImpl(code, true); } @CallerSensitive public Class defineClass(byte[] code, boolean mustBeBootstrap) { Class caller = isNewerThan8 ? Reflection.getCallerClass() : Reflection.getCallerClass(2); if (! caller.getName().startsWith("com.sun.btrace.")) { throw new SecurityException("unsafe defineClass"); } return defineClassImpl(code, mustBeBootstrap); } public void shutdownCmdLine() { exitting.set(true); } /** * Enter method is called by every probed method just * before the probe actions start. */ public static boolean enter(BTraceRuntime current) { if (current.disabled) return false; return rt.get().set(current); } public static boolean enter() { return enter(dummy); } /** * Leave method is called by every probed method just * before the probe actions end (and actual probed * method continues). */ public static void leave() { rt.get().set(null); } /** * start method is called by every BTrace (preprocesed) class * just at the end of it's class initializer. */ public static void start() { BTraceRuntime current = getCurrent(); if (current != null) { current.startImpl(); } } public void handleExit(int exitCode) { exitImpl(exitCode); try { cmdThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public void handleEvent(EventCommand ecmd) { if (eventHandlers != null) { String event = ecmd.getEvent(); final Method eventHandler = eventHandlers.get(event); if (eventHandler != null) { rt.get().escape(new Callable<Void>() { @Override public Void call() throws Exception { eventHandler.invoke(null, (Object[])null); return null; } }); } } } /** * One instance of BTraceRuntime is created per-client. * This forClass method creates it. Class passed is the * preprocessed BTrace program of the client. */ public static BTraceRuntime forClass(Class cl) { BTraceRuntime runtime = runtimes.get(cl.getName()); runtime.init(cl); return runtime; } /** * Utility to create a new ThreadLocal object. Called * by preprocessed BTrace class to create ThreadLocal * for each @TLS variable. * @param initValue Initial value. * This value must be either a boxed primitive or {@linkplain Cloneable}. * In case a {@linkplain Cloneable} value is provided the value is never used directly * - instead, a new clone of the value is created per thread. */ public static ThreadLocal newThreadLocal( final Object initValue) { return new ThreadLocal() { @Override protected Object initialValue() { if (initValue == null) return initValue; if (initValue instanceof Cloneable) { try { Class clz = initValue.getClass(); Method m = clz.getDeclaredMethod("clone"); m.setAccessible(true); return m.invoke(initValue); } catch (Exception e) { e.printStackTrace(); return null; } } return initValue; } }; } // The following constants are copied from VM code // for jvmstat. // perf counter variability - we always variable variability private static final int V_Variable = 3; // perf counter units private static final int V_None = 1; private static final int V_String = 5; private static final int PERF_STRING_LIMIT = 256; /** * Utility to create a new jvmstat perf counter. Called * by preprocessed BTrace class to create perf counter * for each @Export variable. */ public static void newPerfCounter(String name, String desc, Object value) { newPerfCounter(value, name, desc); } public static void newPerfCounter(Object value, String name, String desc) { Perf perf = getPerf(); char tc = desc.charAt(0); switch (tc) { case 'C': case 'Z': case 'B': case 'S': case 'I': case 'J': case 'F': case 'D': { long initValue = (value != null)? ((Number)value).longValue() : 0L; ByteBuffer b = perf.createLong(name, V_Variable, V_None, initValue); b.order(ByteOrder.nativeOrder()); counters.put(name, b); } break; case '[': break; case 'L': { if (desc.equals("Ljava/lang/String;")) { byte[] buf; if (value != null) { buf = getStringBytes((String)value); } else { buf = new byte[PERF_STRING_LIMIT]; buf[0] = '\0'; } ByteBuffer b = perf.createByteArray(name, V_Variable, V_String, buf, buf.length); counters.put(name, b); } } break; } } /** * Return the value of integer perf. counter of given name. */ public static int getPerfInt(String name) { return (int) getPerfLong(name); } /** * Write the value of integer perf. counter of given name. */ public static void putPerfInt(int value, String name) { long l = (long)value; putPerfLong(l, name); } /** * Return the value of float perf. counter of given name. */ public static float getPerfFloat(String name) { int val = getPerfInt(name); return Float.intBitsToFloat(val); } /** * Write the value of float perf. counter of given name. */ public static void putPerfFloat(float value, String name) { int i = Float.floatToRawIntBits(value); putPerfInt(i, name); } /** * Return the value of long perf. counter of given name. */ public static long getPerfLong(String name) { ByteBuffer b = counters.get(name); synchronized(b) { long l = b.getLong(); b.rewind(); return l; } } /** * Write the value of float perf. counter of given name. */ public static void putPerfLong(long value, String name) { ByteBuffer b = counters.get(name); synchronized (b) { b.putLong(value); b.rewind(); } } /** * Return the value of double perf. counter of given name. */ public static double getPerfDouble(String name) { long val = getPerfLong(name); return Double.longBitsToDouble(val); } /** * write the value of double perf. counter of given name. */ public static void putPerfDouble(double value, String name) { long l = Double.doubleToRawLongBits(value); putPerfLong(l, name); } /** * Return the value of String perf. counter of given name. */ public static String getPerfString(String name) { ByteBuffer b = counters.get(name); byte[] buf = new byte[b.limit()]; byte t = (byte)0; int i = 0; synchronized (b) { while ((t = b.get()) != '\0') { buf[i++] = t; } b.rewind(); } try { return new String(buf, 0, i, "UTF-8"); } catch (java.io.UnsupportedEncodingException e) { // ignore, UTF-8 encoding is always known } return ""; } /** * Write the value of float perf. counter of given name. */ public static void putPerfString(String value, String name) { ByteBuffer b = counters.get(name); byte[] v = getStringBytes(value); synchronized (b) { b.put(v); b.rewind(); } } /** * Handles exception from BTrace probe actions. */ public static void handleException(Throwable th) { BTraceRuntime current = getCurrent(); if (current != null) { current.handleExceptionImpl(th); } else { th.printStackTrace(); } } public static String safeStr(Object obj) { if (obj == null) { return "null"; } else if (obj instanceof String) { return (String) obj; } else if (obj.getClass().getClassLoader() == null) { try { String str = obj.toString(); return str; } catch (NullPointerException e) { // NPE can be thrown from inside the toString() method we have no control over return "null"; } catch (Throwable e) { e.printStackTrace(); return "error"; } } else { return identityStr(obj); } } private static String identityStr(Object obj) { int hashCode = java.lang.System.identityHashCode(obj); return obj.getClass().getName() + "@" + Integer.toHexString(hashCode); } // package-private interface to BTraceUtils class. static int speculation() { BTraceRuntime current = getCurrent(); return current.specQueueManager.speculation(); } static void speculate(int id) { BTraceRuntime current = getCurrent(); current.specQueueManager.speculate(id); } static void discard(int id) { BTraceRuntime current = getCurrent(); current.specQueueManager.discard(id); } static void commit(int id) { BTraceRuntime current = getCurrent(); current.specQueueManager.commit(id, current.queue); } /** * Indicates whether two given objects are "equal to" one another. * For bootstrap classes, returns the result of calling Object.equals() * override. For non-bootstrap classes, the reference identity comparison * is done. * * @param obj1 first object to compare equality * @param obj2 second object to compare equality * @return <code>true</code> if the given objects are equal; * <code>false</code> otherwise. */ static boolean compare(Object obj1, Object obj2) { if (obj1 instanceof String) { return obj1.equals(obj2); } else if (obj1.getClass().getClassLoader() == null) { if (obj2 == null || obj2.getClass().getClassLoader() == null) { return obj1.equals(obj2); } // else fall through.. } return obj1 == obj2; } // BTrace map functions static <K, V> Map<K, V> newHashMap() { return new BTraceMap(new HashMap<K, V>()); } static <K, V> Map<K, V> newWeakMap() { return new BTraceMap(new WeakHashMap<K, V>()); } static <V> Deque<V> newDeque() { return new BTraceDeque<>(new ArrayDeque<V>()); } static Appendable newStringBuilder(boolean threadSafe) { return threadSafe ? new StringBuffer() : new StringBuilder(); } static Appendable newStringBuilder() { return newStringBuilder(false); } static <E> int size(Collection<E> coll) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { return coll.size(); } else { throw new IllegalArgumentException(); } } public static <E> boolean isEmpty(Collection<E> coll) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { return coll.isEmpty(); } else { throw new IllegalArgumentException(); } } static <E> boolean contains(Collection<E> coll, Object obj) { if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { for (E e : coll) { if (compare(e, obj)) { return true; } } return false; } else { throw new IllegalArgumentException(); } } static <E >Object[] toArray(Collection<E> collection) { if (collection == null) { return new Object[0]; } else { return collection.toArray(); } } static <K, V> V get(Map<K, V> map, K key) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.get(key); } else { throw new IllegalArgumentException(); } } static <K, V> boolean containsKey(Map<K, V> map, K key) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.containsKey(key); } else { throw new IllegalArgumentException(); } } static <K, V> boolean containsValue(Map<K, V> map, V value) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.containsValue(value); } else { throw new IllegalArgumentException(); } } static <K, V> V put(Map<K, V> map, K key, V value) { if (map instanceof BTraceMap) { return map.put(key, value); } else { throw new IllegalArgumentException("not a btrace map"); } } static <K, V> V remove(Map<K, V> map, K key) { if (map instanceof BTraceMap) { return map.remove(key); } else { throw new IllegalArgumentException("not a btrace map"); } } static <K, V> void clear(Map<K, V> map) { if (map instanceof BTraceMap) { map.clear(); } else { throw new IllegalArgumentException("not a btrace map"); } } static <K, V> int size(Map<K, V> map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.size(); } else { throw new IllegalArgumentException(); } } static <K, V> boolean isEmpty(Map<K, V> map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { return map.isEmpty(); } else { throw new IllegalArgumentException(); } } static <K,V> void putAll(Map<K,V> src, Map<K,V> dst) { dst.putAll(src); } static <K, V> void copy(Map<K,V> src, Map<K,V> dst) { dst.clear(); dst.putAll(src); } static void printMap(Map map) { if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { synchronized(map) { Map<String, String> m = new HashMap<>(); Set<Map.Entry<Object, Object>> entries = map.entrySet(); for (Map.Entry<Object, Object> e : entries) { m.put(BTraceUtils.Strings.str(e.getKey()), BTraceUtils.Strings.str(e.getValue())); } printStringMap(null, m); } } else { print(BTraceUtils.Strings.str(map)); } } public static <V> void push(Deque<V> queue, V value) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.push(value); } else { throw new IllegalArgumentException(); } } public static <V> void addLast(Deque<V> queue, V value) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.addLast(value); } else { throw new IllegalArgumentException(); } } public static <V> V peekFirst(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peekFirst(); } else { throw new IllegalArgumentException(); } } public static <V> V peekLast(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peekLast(); } else { throw new IllegalArgumentException(); } } public static <V> V removeLast(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.removeLast(); } else { throw new IllegalArgumentException(); } } public static <V> V removeFirst(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.removeFirst(); } else { throw new IllegalArgumentException(); } } public static <V> V poll(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.poll(); } else { throw new IllegalArgumentException(); } } public static <V> V peek(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { return queue.peek(); } else { throw new IllegalArgumentException(); } } public static <V> void clear(Deque<V> queue) { if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { queue.clear(); } else { throw new IllegalArgumentException(); } } public static Appendable append(Appendable buffer, String strToAppend) { try { if (buffer != null && strToAppend != null) { return buffer.append(strToAppend); } else { throw new IllegalArgumentException(); } } catch (IOException e) { throw new IllegalArgumentException(e); } } public static int length(Appendable buffer) { if (buffer != null && buffer instanceof CharSequence) { return ((CharSequence)buffer).length(); } else { throw new IllegalArgumentException(); } } static void printNumber(String name, Number value) { getCurrent().send(new NumberDataCommand(name, value)); } static void printNumberMap(String name, Map<String, ? extends Number> data) { getCurrent().send(new NumberMapDataCommand(name, data)); } static void printStringMap(String name, Map<String, String> data) { getCurrent().send(new StringMapDataCommand(name, data)); } // BTrace exit built-in function static void exit(int exitCode) { BTraceRuntime runtime = getCurrent(); if (runtime != null) { Throwable th = runtime.currentException.get(); if (! (th instanceof ExitException)) { runtime.currentException.set(null); } throw new ExitException(exitCode); } } public static void retransform(String runtimeName, Class<?> clazz) { try { BTraceRuntime rt = runtimes.get(runtimeName); if (rt != null && rt.instrumentation.isModifiableClass(clazz)) { rt.instrumentation.retransformClasses(clazz); } } catch (Throwable e) { warning(e); } } static long sizeof(Object obj) { BTraceRuntime runtime = getCurrent(); return runtime.instrumentation.getObjectSize(obj); } // BTrace command line argument functions static int $length() { BTraceRuntime runtime = getCurrent(); return runtime.args == null? 0 : runtime.args.length; } static String $(int n) { BTraceRuntime runtime = getCurrent(); if (runtime.args == null) { return null; } else { if (n >= 0 && n < runtime.args.length) { return runtime.args[n]; } else { return null; } } } /** * @see BTraceUtils#instanceOf(java.lang.Object, java.lang.String) */ static boolean instanceOf(Object obj, String className) { if (obj instanceof AnyType) { // the only time we can have AnyType on stack // is if it was passed in as a placeholder // for void @Return parameter value if (className.equalsIgnoreCase("void")) { return obj.equals(AnyType.VOID); } return false; } Class objClass = obj.getClass(); ClassLoader cl = objClass.getClassLoader(); cl = cl != null ? cl : ClassLoader.getSystemClassLoader(); try { Class target = cl.loadClass(className); return target.isAssignableFrom(objClass); } catch (ClassNotFoundException e) { // non-existing class getCurrent().debugPrint(e); return false; } } private final static class BTraceAtomicInteger extends AtomicInteger { BTraceAtomicInteger(int initVal) { super(initVal); } } static AtomicInteger newAtomicInteger(int initVal) { return new BTraceAtomicInteger(initVal); } static int get(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger || ai.getClass().getClassLoader() == null) { return ai.get(); } else { throw new IllegalArgumentException(); } } static void set(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { ai.set(i); } else { throw new IllegalArgumentException(); } } static void lazySet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { ai.lazySet(i); } else { throw new IllegalArgumentException(); } } static boolean compareAndSet(AtomicInteger ai, int i, int j) { if (ai instanceof BTraceAtomicInteger) { return ai.compareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static boolean weakCompareAndSet(AtomicInteger ai, int i, int j) { if (ai instanceof BTraceAtomicInteger) { return ai.weakCompareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static int getAndIncrement(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndIncrement(); } else { throw new IllegalArgumentException(); } } static int getAndDecrement(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndDecrement(); } else { throw new IllegalArgumentException(); } } static int incrementAndGet(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.incrementAndGet(); } else { throw new IllegalArgumentException(); } } static int decrementAndGet(AtomicInteger ai) { if (ai instanceof BTraceAtomicInteger) { return ai.decrementAndGet(); } else { throw new IllegalArgumentException(); } } static int getAndAdd(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndAdd(i); } else { throw new IllegalArgumentException(); } } static int addAndGet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.addAndGet(i); } else { throw new IllegalArgumentException(); } } static int getAndSet(AtomicInteger ai, int i) { if (ai instanceof BTraceAtomicInteger) { return ai.getAndSet(i); } else { throw new IllegalArgumentException(); } } private final static class BTraceAtomicLong extends AtomicLong { BTraceAtomicLong(long initVal) { super(initVal); } } static AtomicLong newAtomicLong(long initVal) { return new BTraceAtomicLong(initVal); } static long get(AtomicLong al) { if (al instanceof BTraceAtomicLong || al.getClass().getClassLoader() == null) { return al.get(); } else { throw new IllegalArgumentException(); } } static void set(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { al.set(i); } else { throw new IllegalArgumentException(); } } static void lazySet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { al.lazySet(i); } else { throw new IllegalArgumentException(); } } static boolean compareAndSet(AtomicLong al, long i, long j) { if (al instanceof BTraceAtomicLong) { return al.compareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static boolean weakCompareAndSet(AtomicLong al, long i, long j) { if (al instanceof BTraceAtomicLong) { return al.weakCompareAndSet(i, j); } else { throw new IllegalArgumentException(); } } static long getAndIncrement(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.getAndIncrement(); } else { throw new IllegalArgumentException(); } } static long getAndDecrement(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.getAndDecrement(); } else { throw new IllegalArgumentException(); } } static long incrementAndGet(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.incrementAndGet(); } else { throw new IllegalArgumentException(); } } static long decrementAndGet(AtomicLong al) { if (al instanceof BTraceAtomicLong) { return al.decrementAndGet(); } else { throw new IllegalArgumentException(); } } static long getAndAdd(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.getAndAdd(i); } else { throw new IllegalArgumentException(); } } static long addAndGet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.addAndGet(i); } else { throw new IllegalArgumentException(); } } static long getAndSet(AtomicLong al, long i) { if (al instanceof BTraceAtomicLong) { return al.getAndSet(i); } else { throw new IllegalArgumentException(); } } // BTrace perf counter reading functions static int perfInt(String name) { return getPerfReader().perfInt(name); } static long perfLong(String name) { return getPerfReader().perfLong(name); } static String perfString(String name) { return getPerfReader().perfString(name); } // the number of stack frames taking a thread dump adds private static final int THRD_DUMP_FRAMES = 1; // stack trace functions private static String stackTraceAllStr(int numFrames, boolean printWarning) { Set<Map.Entry<Thread, StackTraceElement[]>> traces = Thread.getAllStackTraces().entrySet(); StringBuilder buf = new StringBuilder(); for (Map.Entry<Thread, StackTraceElement[]> t : traces) { buf.append(t.getKey().toString()); buf.append(LINE_SEPARATOR); buf.append(LINE_SEPARATOR); StackTraceElement[] st = t.getValue(); buf.append(stackTraceStr("\t", st, 0, numFrames, printWarning)); buf.append(LINE_SEPARATOR); } return buf.toString(); } static String stackTraceAllStr(int numFrames) { return stackTraceAllStr(numFrames, false); } static void stackTraceAll(int numFrames) { getCurrent().send(stackTraceAllStr(numFrames, true)); } static String stackTraceStr(StackTraceElement[] st, int strip, int numFrames) { return stackTraceStr(null, st, strip, numFrames, false); } static String stackTraceStr(String prefix, StackTraceElement[] st, int strip, int numFrames) { return stackTraceStr(prefix, st, strip, numFrames, false); } private static String stackTraceStr(String prefix, StackTraceElement[] st, int strip, int numFrames, boolean printWarning) { strip = strip > 0 ? strip + THRD_DUMP_FRAMES : 0; numFrames = numFrames > 0 ? numFrames : st.length - strip; int limit = strip + numFrames; limit = limit <= st.length ? limit : st.length; if (prefix == null) { prefix = ""; } StringBuilder buf = new StringBuilder(); for (int i = strip; i < limit; i++) { buf.append(prefix); buf.append(st[i].toString()); buf.append(LINE_SEPARATOR); } if (printWarning && limit < st.length) { buf.append(prefix); buf.append(st.length - limit); buf.append(" more frame(s) ..."); buf.append(LINE_SEPARATOR); } return buf.toString(); } static void stackTrace(StackTraceElement[] st, int strip, int numFrames) { stackTrace(null, st, strip, numFrames); } static void stackTrace(String prefix, StackTraceElement[] st, int strip, int numFrames) { getCurrent().send(stackTraceStr(prefix, st, strip, numFrames, true)); } // print/println functions static void print(String str) { getCurrent().send(str); } static void println(String str) { getCurrent().send(str + LINE_SEPARATOR); } static void println() { getCurrent().send(LINE_SEPARATOR); } static String property(String name) { return AccessController.doPrivileged( new GetPropertyAction(name)); } static Properties properties() { return AccessController.doPrivileged( new PrivilegedAction<Properties>() { @Override public Properties run() { return System.getProperties(); } } ); } static String getenv(final String name) { return AccessController.doPrivileged( new PrivilegedAction<String>() { @Override public String run() { return System.getenv(name); } } ); } static Map<String, String> getenv() { return AccessController.doPrivileged( new PrivilegedAction<Map<String, String>>() { @Override public Map<String, String> run() { return System.getenv(); } } ); } static MemoryUsage heapUsage() { initMemoryMBean(); return memoryMBean.getHeapMemoryUsage(); } static MemoryUsage nonHeapUsage() { initMemoryMBean(); return memoryMBean.getNonHeapMemoryUsage(); } static long finalizationCount() { initMemoryMBean(); return memoryMBean.getObjectPendingFinalizationCount(); } static long vmStartTime() { initRuntimeMBean(); return runtimeMBean.getStartTime(); } static long vmUptime() { initRuntimeMBean(); return runtimeMBean.getUptime(); } static List<String> getInputArguments() { initRuntimeMBean(); return runtimeMBean.getInputArguments(); } static String getVmVersion() { initRuntimeMBean(); return runtimeMBean.getVmVersion(); } static boolean isBootClassPathSupported() { initRuntimeMBean(); return runtimeMBean.isBootClassPathSupported(); } static String getBootClassPath() { initRuntimeMBean(); return runtimeMBean.getBootClassPath(); } static long getThreadCount() { initThreadMBean(); return threadMBean.getThreadCount(); } static long getPeakThreadCount() { initThreadMBean(); return threadMBean.getPeakThreadCount(); } static long getTotalStartedThreadCount() { initThreadMBean(); return threadMBean.getTotalStartedThreadCount(); } static long getDaemonThreadCount() { initThreadMBean(); return threadMBean.getDaemonThreadCount(); } static long getCurrentThreadCpuTime() { initThreadMBean(); threadMBean.setThreadCpuTimeEnabled(true); return threadMBean.getCurrentThreadCpuTime(); } static long getCurrentThreadUserTime() { initThreadMBean(); threadMBean.setThreadCpuTimeEnabled(true); return threadMBean.getCurrentThreadUserTime(); } static void dumpHeap(String fileName, boolean live) { initHotspotMBean(); try { String name = resolveFileName(fileName); hotspotMBean.dumpHeap(name, live); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } static long getTotalGcTime() { initGarbageCollectionBeans(); long totalGcTime = 0; for (GarbageCollectorMXBean gcBean : gcBeanList) { totalGcTime += gcBean.getCollectionTime(); } return totalGcTime; } static String getMemoryPoolUsage(String poolFormat) { if (poolFormat == null) { poolFormat = "%1$s;%2$d;%3$d;%4$d;%5$d"; } Object[][] poolOutput = new Object[memPoolList.size()][5]; StringBuilder membuffer = new StringBuilder(); for (int i = 0; i < memPoolList.size(); i++) { MemoryPoolMXBean memPool = memPoolList.get(i); poolOutput[i][0] = memPool.getName(); poolOutput[i][1] = memPool.getUsage().getMax(); poolOutput[i][2] = memPool.getUsage().getUsed(); poolOutput[i][3] = memPool.getUsage().getCommitted(); poolOutput[i][4] = memPool.getUsage().getInit(); } for (Object[] memPoolOutput : poolOutput) { membuffer.append(String.format(poolFormat, memPoolOutput)).append("\n"); } return membuffer.toString(); } static double getSystemLoadAverage() { initOperatingSystemBean(); return operatingSystemMXBean.getSystemLoadAverage(); } static long getProcessCPUTime() { initOperatingSystemBean(); if (operatingSystemMXBean instanceof com.sun.management.OperatingSystemMXBean) { return ((com.sun.management.OperatingSystemMXBean)operatingSystemMXBean).getProcessCpuTime(); } return -1; } static void serialize(Object obj, String fileName) { try { BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(resolveFileName(fileName))); try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(obj); } } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } static String toXML(Object obj) { try { return XMLSerializer.toXML(obj); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } static void writeXML(Object obj, String fileName) { try { Path p = FileSystems.getDefault().getPath(resolveFileName(fileName)); try (BufferedWriter bw = Files.newBufferedWriter(p, StandardCharsets.UTF_8)) { XMLSerializer.write(obj, bw); } } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } private synchronized static void initDOTWriterProps() { if (dotWriterProps == null) { dotWriterProps = new Properties(); InputStream is = BTraceRuntime.class.getResourceAsStream("resources/btrace.dotwriter.properties"); if (is != null) { try { dotWriterProps.load(is); } catch (IOException ioExp) { ioExp.printStackTrace(); } } try { String home = System.getProperty("user.home"); File file = new File(home, "btrace.dotwriter.properties"); if (file.exists() && file.isFile()) { is = new BufferedInputStream(new FileInputStream(file)); dotWriterProps.load(is); } } catch (Exception exp) { exp.printStackTrace(); } } } static void writeDOT(Object obj, String fileName) { DOTWriter writer = new DOTWriter(resolveFileName(fileName)); initDOTWriterProps(); writer.customize(dotWriterProps); writer.addNode(null, obj); writer.close(); } private static String INDENT = " "; static void deadlocks(boolean stackTrace) { initThreadMBean(); if (threadMBean.isSynchronizerUsageSupported()) { long[] tids = threadMBean.findDeadlockedThreads(); if (tids != null && tids.length > 0) { ThreadInfo[] infos = threadMBean.getThreadInfo(tids, true, true); StringBuilder sb = new StringBuilder(); for (ThreadInfo ti : infos) { sb.append("\"").append(ti.getThreadName()).append("\"" + " Id=").append(ti.getThreadId()).append(" in ").append(ti.getThreadState()); if (ti.getLockName() != null) { sb.append(" on lock=").append(ti.getLockName()); } if (ti.isSuspended()) { sb.append(" (suspended)"); } if (ti.isInNative()) { sb.append(" (running in native)"); } if (ti.getLockOwnerName() != null) { sb.append(INDENT).append(" owned by ").append(ti.getLockOwnerName()).append(" Id=").append(ti.getLockOwnerId()); sb.append(LINE_SEPARATOR); } if (stackTrace) { // print stack trace with locks StackTraceElement[] stacktrace = ti.getStackTrace(); MonitorInfo[] monitors = ti.getLockedMonitors(); for (int i = 0; i < stacktrace.length; i++) { StackTraceElement ste = stacktrace[i]; sb.append(INDENT).append("at ").append(ste.toString()); sb.append(LINE_SEPARATOR); for (MonitorInfo mi : monitors) { if (mi.getLockedStackDepth() == i) { sb.append(INDENT).append(" - locked ").append(mi); sb.append(LINE_SEPARATOR); } } } sb.append(LINE_SEPARATOR); } LockInfo[] locks = ti.getLockedSynchronizers(); sb.append(INDENT).append("Locked synchronizers: count = ").append(locks.length); sb.append(LINE_SEPARATOR); for (LockInfo li : locks) { sb.append(INDENT).append(" - ").append(li); sb.append(LINE_SEPARATOR); } sb.append(LINE_SEPARATOR); } getCurrent().send(sb.toString()); } } } static int dtraceProbe(String s1, String s2, int i1, int i2) { if (dtraceEnabled) { return dtraceProbe0(s1, s2, i1, i2); } else { return 0; } } // BTrace aggregation support static Aggregation newAggregation(AggregationFunction type) { return new Aggregation(type); } static AggregationKey newAggregationKey(Object... elements) { return new AggregationKey(elements); } static void addToAggregation(Aggregation aggregation, long value) { aggregation.add(value); } static void addToAggregation(Aggregation aggregation, AggregationKey key, long value) { aggregation.add(key, value); } static void clearAggregation(Aggregation aggregation) { aggregation.clear(); } static void truncateAggregation(Aggregation aggregation, int count) { aggregation.truncate(count); } static void printAggregation(String name, Aggregation aggregation) { getCurrent().send(new GridDataCommand(name, aggregation.getData())); } static void printSnapshot(String name, Profiler.Snapshot snapshot) { getCurrent().send(new GridDataCommand(name, snapshot.getGridData())); } /** * Prints profiling snapshot using the provided format * @param name The name of the aggregation to be used in the textual output * @param snapshot The snapshot to print * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, java.lang.Object[]) } behaviour * with the addition of the ability to address the key title as a 0-indexed item * @see String#format(java.lang.String, java.lang.Object[]) */ static void printSnapshot(String name, Profiler.Snapshot snapshot, String format) { getCurrent().send(new GridDataCommand(name, snapshot.getGridData(), format)); } /** * Precondition: Only values from the first Aggregation are printed. If the subsequent aggregations have * values for keys which the first aggregation does not have, these rows are ignored. * @param name * @param format * @param aggregationArray */ static void printAggregation(String name, String format, Aggregation[] aggregationArray) { if (aggregationArray.length > 1 && aggregationArray[0].getKeyData().size() > 1) { int aggregationDataSize = aggregationArray[0].getKeyData().get(0).getElements().length + aggregationArray.length; List<Object[]> aggregationData = new ArrayList<>(); //Iterate through all keys in the first Aggregation and build up an array of aggregationData for (AggregationKey aggKey : aggregationArray[0].getKeyData()) { int aggDataIndex = 0; Object[] currAggregationData = new Object[aggregationDataSize]; //Add the key to the from of the current aggregation Data for (Object obj : aggKey.getElements()) { currAggregationData[aggDataIndex] = obj; aggDataIndex++; } for (Aggregation agg : aggregationArray) { currAggregationData[aggDataIndex] = agg.getValueForKey(aggKey); aggDataIndex++; } aggregationData.add(currAggregationData); } getCurrent().send(new GridDataCommand(name, aggregationData, format)); } } /** * Prints aggregation using the provided format * @param name The name of the aggregation to be used in the textual output * @param aggregation The aggregation to print * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, java.lang.Object[]) } behaviour * with the addition of the ability to address the key title as a 0-indexed item * @see String#format(java.lang.String, java.lang.Object[]) */ static void printAggregation(String name, Aggregation aggregation, String format) { getCurrent().send(new GridDataCommand(name, aggregation.getData(), format)); } // profiling related methods /** * @see BTraceUtils.Profiling#newProfiler() */ static Profiler newProfiler() { return new MethodInvocationProfiler(600); } /** * @see BTraceUtils.Profiling#newProfiler(int) */ static Profiler newProfiler(int expectedMethodCnt) { return new MethodInvocationProfiler(expectedMethodCnt); } /** * @see BTraceUtils.Profiling#recordEntry(com.sun.btrace.Profiler, java.lang.String) */ static void recordEntry(Profiler profiler, String methodName) { profiler.recordEntry(methodName); } /** * @see BTraceUtils.Profiling#recordExit(com.sun.btrace.Profiler, java.lang.String, long) */ static void recordExit(Profiler profiler, String methodName, long duration) { profiler.recordExit(methodName, duration); } /** * @see BTraceUtils.Profiling#snapshot(com.sun.btrace.Profiler) */ static Profiler.Snapshot snapshot(Profiler profiler) { return profiler.snapshot(); } /** * @see BTraceUtils.Profiling#snapshotAndReset(com.sun.btrace.Profiler) */ static Profiler.Snapshot snapshotAndReset(Profiler profiler) { return profiler.snapshot(true); } static void resetProfiler(Profiler profiler) { profiler.reset(); } // private methods below this point // raise DTrace USDT probe private static native int dtraceProbe0(String s1, String s2, int i1, int i2); private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; /** * Get the current thread BTraceRuntime instance * if there is one. */ private static BTraceRuntime getCurrent() { BTraceRuntime current = rt.get().rt; assert current != null : "BTraceRuntime is null!"; return current; } private void initThreadPool() { if (threadPool == null) { synchronized (this) { if (threadPool == null) { threadPool = Executors.newFixedThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread th = new Thread(r); th.setDaemon(true); return th; } }); } } } } private static void initHotspotMBean() { if (hotspotMBean == null) { synchronized (BTraceRuntime.class) { if (hotspotMBean == null) { hotspotMBean = getHotspotMBean(); } } } } private static HotSpotDiagnosticMXBean getHotspotMBean() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<HotSpotDiagnosticMXBean>() { @Override public HotSpotDiagnosticMXBean run() throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); Set<ObjectName> s = server.queryNames(new ObjectName(HOTSPOT_BEAN_NAME), null); Iterator<ObjectName> itr = s.iterator(); if (itr.hasNext()) { ObjectName name = itr.next(); HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy(server, name.toString(), HotSpotDiagnosticMXBean.class); return bean; } else { return null; } } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static void initMemoryMBean() { if (memoryMBean == null) { synchronized (BTraceRuntime.class) { if (memoryMBean == null) { memoryMBean = getMemoryMBean(); } } } } private void initMemoryListener() { initThreadPool(); memoryListener = new NotificationListener() { @Override @SuppressWarnings("FutureReturnValueIgnored") public void handleNotification(Notification notif, Object handback) { boolean entered = BTraceRuntime.enter(); try { String notifType = notif.getType(); if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) { CompositeData cd = (CompositeData) notif.getUserData(); final MemoryNotificationInfo info = MemoryNotificationInfo.from(cd); String name = info.getPoolName(); final Method handler = lowMemHandlers.get(name); if (handler != null) { threadPool.submit(new Runnable() { @Override public void run() { boolean entered = BTraceRuntime.enter(); try { if (handler.getParameterTypes().length == 1) { handler.invoke(null, info.getUsage()); } else { handler.invoke(null, (Object[])null); } } catch (Throwable th) { } finally { if (entered) { BTraceRuntime.leave(); } } } }); } } } finally { if (entered) { BTraceRuntime.leave(); } } } }; } private static MemoryMXBean getMemoryMBean() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<MemoryMXBean>() { @Override public MemoryMXBean run() throws Exception { return ManagementFactory.getMemoryMXBean(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static void initRuntimeMBean() { if (runtimeMBean == null) { synchronized (BTraceRuntime.class) { if (runtimeMBean == null) { runtimeMBean = getRuntimeMBean(); } } } } private static RuntimeMXBean getRuntimeMBean() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<RuntimeMXBean>() { @Override public RuntimeMXBean run() throws Exception { return ManagementFactory.getRuntimeMXBean(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static void initThreadMBean() { if (threadMBean == null) { synchronized (BTraceRuntime.class) { if (threadMBean == null) { threadMBean = getThreadMBean(); } } } } private static ThreadMXBean getThreadMBean() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<ThreadMXBean>() { @Override public ThreadMXBean run() throws Exception { return ManagementFactory.getThreadMXBean(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static List<MemoryPoolMXBean> getMemoryPoolMXBeans() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<List<MemoryPoolMXBean>>() { @Override public List<MemoryPoolMXBean> run() throws Exception { return ManagementFactory.getMemoryPoolMXBeans(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static List<GarbageCollectorMXBean> getGarbageCollectionMBeans() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<List<GarbageCollectorMXBean>>() { @Override public List<GarbageCollectorMXBean> run() throws Exception { return ManagementFactory.getGarbageCollectorMXBeans(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static void initGarbageCollectionBeans() { if (gcBeanList == null) { synchronized (BTraceRuntime.class) { if (gcBeanList == null) { gcBeanList = getGarbageCollectionMBeans(); } } } } private static void initMemoryPoolList() { if (memPoolList == null) { synchronized (BTraceRuntime.class) { if (memPoolList == null) { memPoolList = getMemoryPoolMXBeans(); } } } } private static OperatingSystemMXBean getOperatingSystemMXBean() { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<OperatingSystemMXBean>() { @Override public OperatingSystemMXBean run() throws Exception { return ManagementFactory.getOperatingSystemMXBean(); } }); } catch (Exception exp) { throw new UnsupportedOperationException(exp); } } private static void initOperatingSystemBean() { if (operatingSystemMXBean == null) { synchronized (BTraceRuntime.class) { if (operatingSystemMXBean == null) { operatingSystemMXBean = getOperatingSystemMXBean(); } } } } private static PerfReader getPerfReader() { if (perfReader == null) { throw new UnsupportedOperationException(); } return perfReader; } private static RunnableGenerator getRunnableGenerator() { return runnableGenerator; } private void send(String msg) { send(new MessageCommand(messageTimestamp? System.nanoTime() : 0L, msg)); } public void send(Command cmd) { boolean speculated = specQueueManager.send(cmd); if (! speculated) { enqueue(cmd); } } private void enqueue(Command cmd) { int backoffCntr = 0; while (!queue.relaxedOffer(cmd)) { try { if (backoffCntr < 3000) { Thread.yield(); } else if (backoffCntr < 3100) { Thread.sleep(1); } else { Thread.sleep(100); } } catch (InterruptedException e) {} backoffCntr++; } } private void handleExceptionImpl(Throwable th) { if (currentException.get() != null) { return; } leave(); currentException.set(th); try { if (th instanceof ExitException) { exitImpl(((ExitException)th).exitCode()); } else { if (exceptionHandler != null) { try { exceptionHandler.invoke(null, th); } catch (Throwable ignored) { } } else { // Do not call send(Command). Exception messages should not // go to speculative buffers! enqueue(new ErrorCommand(th)); } } } finally { currentException.set(null); } } private void startImpl() { if (timerHandlers != null && timerHandlers.length != 0) { timer = new Timer(true); RunnableGenerator gen = getRunnableGenerator(); Runnable[] runnables = new Runnable[timerHandlers.length]; if (gen != null) { generateRunnables(gen, runnables); } else { wrapToRunnables(runnables); } for (int index = 0; index < timerHandlers.length; index++) { Method m = timerHandlers[index]; OnTimer tp = m.getAnnotation(OnTimer.class); long period = tp.value(); final Runnable r = runnables[index]; timer.schedule(new TimerTask() { @Override public void run() { r.run(); } }, period, period); } } if (! lowMemHandlers.isEmpty()) { initMemoryMBean(); initMemoryListener(); NotificationEmitter emitter = (NotificationEmitter) memoryMBean; emitter.addNotificationListener(memoryListener, null, null); } leave(); } private void generateRunnables(RunnableGenerator gen, Runnable[] runnables) { final MemoryClassLoader loader = AccessController.doPrivileged( new PrivilegedAction<MemoryClassLoader>() { @Override public MemoryClassLoader run() { return new MemoryClassLoader(clazz.getClassLoader()); } }); for (int index = 0; index < timerHandlers.length; index++) { Method m = timerHandlers[index]; try { final String clzName = "com/sun/btrace/BTraceRunnable$" + index; final byte[] buf = gen.generate(m, clzName); Class cls = AccessController.doPrivileged( new PrivilegedExceptionAction<Class>() { @Override public Class run() throws Exception { return loader.loadClass(clzName.replace('/', '.'), buf); } }); runnables[index] = (Runnable) cls.getDeclaredConstructor().newInstance(); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } } private void wrapToRunnables(Runnable[] runnables) { for (int index = 0; index < timerHandlers.length; index++) { final Method m = timerHandlers[index]; runnables[index] = new Runnable() { @Override public void run() { try { m.invoke(null, (Object[])null); } catch (Throwable th) { } } }; } } private synchronized void exitImpl(int exitCode) { try { if (timer != null) { timer.cancel(); } if (memoryListener != null && memoryMBean != null) { NotificationEmitter emitter = (NotificationEmitter) memoryMBean; try { emitter.removeNotificationListener(memoryListener); } catch (ListenerNotFoundException lnfe) {} } if (threadPool != null) { threadPool.shutdownNow(); } if (exitHandler != null) { boolean entered = false; try { entered = BTraceRuntime.enter(this); exitHandler.invoke(null, exitCode); } catch (Throwable ignored) { } finally { exitHandler = null; if (entered) { BTraceRuntime.leave(); } } } send(new ExitCommand(exitCode)); } finally { disabled = true; } } private static Perf getPerf() { if (perf == null) { synchronized(BTraceRuntime.class) { if (perf == null) { perf = (Perf) AccessController.doPrivileged(new Perf.GetPerfAction()); } } } return perf; } private static byte[] getStringBytes(String value) { byte[] v = null; try { v = value.getBytes("UTF-8"); } catch (java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } byte[] v1 = new byte[v.length+1]; System.arraycopy(v, 0, v1, 0, v.length); v1[v.length] = '\0'; return v1; } private Class defineClassImpl(byte[] code, boolean mustBeBootstrap) { ClassLoader loader = null; if (! mustBeBootstrap) { loader = new ClassLoader(null) {}; } Class cl = unsafe.defineClass(className, code, 0, code.length, loader, null); unsafe.ensureClassInitialized(cl); return cl; } private void init(Class cl) { if (this.clazz != null) { return; } this.clazz = cl; List<Method> timersList = new ArrayList<>(); this.eventHandlers = new HashMap<>(); this.lowMemHandlers = new HashMap<>(); Method[] methods = clazz.getMethods(); for (Method m : methods) { int modifiers = m.getModifiers(); if (! Modifier.isStatic(modifiers)) { continue; } OnEvent oev = m.getAnnotation(OnEvent.class); if (oev != null && m.getParameterTypes().length == 0) { eventHandlers.put(oev.value(), m); } OnError oer = m.getAnnotation(OnError.class); if (oer != null) { Class[] argTypes = m.getParameterTypes(); if (argTypes.length == 1 && argTypes[0] == Throwable.class) { this.exceptionHandler = m; } } OnExit oex = m.getAnnotation(OnExit.class); if (oex != null) { Class[] argTypes = m.getParameterTypes(); if (argTypes.length == 1 && argTypes[0] == int.class) { this.exitHandler = m; } } OnTimer ot = m.getAnnotation(OnTimer.class); if (ot != null && m.getParameterTypes().length == 0) { timersList.add(m); } OnLowMemory olm = m.getAnnotation(OnLowMemory.class); if (olm != null) { Class[] argTypes = m.getParameterTypes(); if ((argTypes.length == 0) || (argTypes.length == 1 && argTypes[0] == MemoryUsage.class)) { lowMemHandlers.put(olm.pool(), m); } } } initMemoryPoolList(); for (MemoryPoolMXBean mpoolBean : memPoolList) { String name = mpoolBean.getName(); if (lowMemHandlers.containsKey(name)) { Method m = lowMemHandlers.get(name); OnLowMemory olm = m.getAnnotation(OnLowMemory.class); if (mpoolBean.isUsageThresholdSupported()) { mpoolBean.setUsageThreshold(olm.threshold()); } } } timerHandlers = new Method[timersList.size()]; timersList.toArray(timerHandlers); try { level = cl.getDeclaredField("$btrace$$level"); level.setAccessible(true); } catch (Throwable e) { debugPrint("Instrumentation level setting not available"); } BTraceMBean.registerMBean(clazz); } private static String resolveFileName(String name) { if (name.indexOf(File.separatorChar) != -1) { throw new IllegalArgumentException("directories are not allowed"); } StringBuilder buf = new StringBuilder(); buf.append('.'); buf.append(File.separatorChar); BTraceRuntime runtime = getCurrent(); buf.append("btrace"); if (runtime.args != null && runtime.args.length > 0) { buf.append(runtime.args[0]); } buf.append(File.separatorChar); buf.append(runtime.className); new File(buf.toString()).mkdirs(); buf.append(File.separatorChar); buf.append(name); return buf.toString(); } private static void loadLibrary(final ClassLoader cl) { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { loadBTraceLibrary(cl); return null; } }); } private static void loadBTraceLibrary(final ClassLoader loader) { boolean isSolaris = System.getProperty("os.name").equals("SunOS"); if (isSolaris) { try { System.loadLibrary("btrace"); dtraceEnabled = true; } catch (LinkageError le) { if (loader == null || loader.getResource("com/sun/btrace") == null) { warning("cannot load libbtrace.so, will miss DTrace probes from BTrace"); return; } String path = loader.getResource("com/sun/btrace").toString(); int archSeparator = path.indexOf('!'); if (archSeparator != -1) { path = path.substring(0, archSeparator); path = path.substring("jar:".length(), path.lastIndexOf('/')); } else { int buildSeparator = path.indexOf("/classes/"); if (buildSeparator != -1) { path = path.substring(0, buildSeparator); } } String cpu = System.getProperty("os.arch"); if (cpu.equals("x86")) { cpu = "i386"; } path += "/" + cpu + "/libbtrace.so"; try { path = new File(new URI(path)).getAbsolutePath(); } catch (RuntimeException re) { throw re; } catch (Exception e) { throw new RuntimeException(e); } try { System.load(path); dtraceEnabled = true; } catch (LinkageError le1) { warning("cannot load libbtrace.so, will miss DTrace probes from BTrace"); } } } } private static void setupCmdQueueParams() { String maxQLen = System.getProperty(CMD_QUEUE_LIMIT_KEY, null); if (maxQLen == null) { CMD_QUEUE_LIMIT = CMD_QUEUE_LIMIT_DEFAULT; // debugPrint("\"" + CMD_QUEUE_LIMIT_KEY + "\" not provided. " + // "Using the default cmd queue limit of " + CMD_QUEUE_LIMIT_DEFAULT); } else { try { CMD_QUEUE_LIMIT = Integer.parseInt(maxQLen); // debugPrint("The cmd queue limit set to " + CMD_QUEUE_LIMIT); } catch (NumberFormatException e) { warning("\"" + maxQLen + "\" is not a valid int number. " + "Using the default cmd queue limit of " + CMD_QUEUE_LIMIT_DEFAULT); CMD_QUEUE_LIMIT = CMD_QUEUE_LIMIT_DEFAULT; } } } private void debugPrint(String msg) { debug.debug(msg); } private void debugPrint(Throwable t) { debug.debug(t); } private static void warning(String msg) { DebugSupport.warning(msg); } private static void warning(Throwable t) { DebugSupport.warning(t); } }