All Downloads are FREE. Search and download functionalities are using the official Maven repository.

studio.raptor.sqlparser.fast.util.Profiler Maven / Gradle / Ivy

/*
 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package studio.raptor.sqlparser.fast.util;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * A simple CPU profiling tool similar to java -Xrunhprof. It can be used
 * in-process (to profile the current application) or as a standalone program
 * (to profile a different process, or files containing full thread dumps).
 */
public class Profiler implements Runnable {

  private static final String LINE_SEPARATOR =
      System.getProperty("line.separator", "\n");
  private static final int MAX_ELEMENTS = 1000;
  private static Instrumentation instrumentation;
  private final String[] ignoreLines = (
      "java," +
          "sun," +
          "com.sun.," +
          "com.google.common.," +
          "com.mongodb.," +
          "org.bson.,"
  ).split(",");
  private final String[] ignorePackages = (
      "java," +
          "sun," +
          "com.sun.," +
          "com.google.common.," +
          "com.mongodb.," +
          "org.bson"
  ).split(",");
  private final String[] ignoreThreads = (
      "java.lang.Object.wait," +
          "java.lang.Thread.dumpThreads," +
          "java.lang.Thread.getThreads," +
          "java.lang.Thread.sleep," +
          "java.lang.UNIXProcess.waitForProcessExit," +
          "java.net.PlainDatagramSocketImpl.receive0," +
          "java.net.PlainSocketImpl.accept," +
          "java.net.PlainSocketImpl.socketAccept," +
          "java.net.SocketInputStream.socketRead," +
          "java.net.SocketOutputStream.socketWrite," +
          "org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect," +
          "sun.awt.windows.WToolkit.eventLoop," +
          "sun.misc.Unsafe.park," +
          "sun.nio.ch.EPollArrayWrapper.epollWait," +
          "sun.nio.ch.KQueueArrayWrapper.kevent0," +
          "sun.nio.ch.ServerSocketChannelImpl.accept," +
          "dalvik.system.VMStack.getThreadStackTrace," +
          "dalvik.system.NativeStart.run"
  ).split(",");
  private final HashMap counts =
      new HashMap();
  /**
   * The summary (usually one entry per package, unless sumClasses is enabled,
   * in which case it's one entry per class).
   */
  private final HashMap summary =
      new HashMap();
  public int interval = 2;
  public int depth = 48;
  public boolean paused;
  public boolean sumClasses;
  public boolean sumMethods;
  private int pid;
  private volatile boolean stop;
  private int minCount = 1;
  private int total;
  private Thread thread;
  private long start;
  private long time;
  private int threadDumps;

  /**
   * This method is called when the agent is installed.
   *
   * @param agentArgs the agent arguments
   * @param inst the instrumentation object
   */
  public static void premain(String agentArgs, Instrumentation inst) {
    instrumentation = inst;
  }

  /**
   * Get the instrumentation object if started as an agent.
   *
   * @return the instrumentation, or null
   */
  public static Instrumentation getInstrumentation() {
    return instrumentation;
  }

  /**
   * Run the command line version of the profiler. The JDK (jps and jstack)
   * need to be in the path.
   *
   * @param args the process id of the process - if not set the java processes are listed
   */
  public static void main(String... args) {
    new Profiler().run(args);
  }

  private static List getRunnableStackTraces() {
    ArrayList list = new ArrayList();
    Map map = Thread.getAllStackTraces();
    for (Map.Entry entry : map.entrySet()) {
      Thread t = entry.getKey();
      if (t.getState() != Thread.State.RUNNABLE) {
        continue;
      }
      StackTraceElement[] dump = entry.getValue();
      if (dump == null || dump.length == 0) {
        continue;
      }
      list.add(dump);
    }
    return list;
  }

  private static List readRunnableStackTraces(int pid) {
    try {
      String jstack = exec("jstack", "" + pid);
      LineNumberReader r = new LineNumberReader(
          new StringReader(jstack));
      return readStackTrace(r);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private static List readStackTrace(LineNumberReader r)
      throws IOException {
    ArrayList list = new ArrayList();
    while (true) {
      String line = r.readLine();
      if (line == null) {
        break;
      }
      if (!line.startsWith("\"")) {
        // not a thread
        continue;
      }
      line = r.readLine();
      if (line == null) {
        break;
      }
      line = line.trim();
      if (!line.startsWith("java.lang.Thread.State: RUNNABLE")) {
        continue;
      }
      ArrayList stack = new ArrayList();
      while (true) {
        line = r.readLine();
        if (line == null) {
          break;
        }
        line = line.trim();
        if (line.startsWith("- ")) {
          continue;
        }
        if (!line.startsWith("at ")) {
          break;
        }
        line = line.substring(3).trim();
        stack.add(line);
      }
      if (stack.size() > 0) {
        String[] s = stack.toArray(new String[stack.size()]);
        list.add(s);
      }
    }
    return list;
  }

  private static String exec(String... args) {
    ByteArrayOutputStream err = new ByteArrayOutputStream();
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
      Process p = Runtime.getRuntime().exec(args);
      copyInThread(p.getInputStream(), out);
      copyInThread(p.getErrorStream(), err);
      p.waitFor();
      String e = new String(err.toByteArray(), "UTF-8");
      if (e.length() > 0) {
        throw new RuntimeException(e);
      }
      String output = new String(out.toByteArray(), "UTF-8");
      return output;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private static void copyInThread(final InputStream in,
      final OutputStream out) {
    new Thread("Profiler stream copy") {
      @Override
      public void run() {
        byte[] buffer = new byte[4096];
        try {
          while (true) {
            int len = in.read(buffer, 0, buffer.length);
            if (len < 0) {
              break;
            }
            out.write(buffer, 0, len);
          }
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }.start();
  }

  private static boolean startsWithAny(String s, String[] prefixes) {
    for (String p : prefixes) {
      if (p.length() > 0 && s.startsWith(p)) {
        return true;
      }
    }
    return false;
  }

  private static int increment(HashMap map, String trace,
      int minCount) {
    Integer oldCount = map.get(trace);
    if (oldCount == null) {
      map.put(trace, 1);
    } else {
      map.put(trace, oldCount + 1);
    }
    while (map.size() > MAX_ELEMENTS) {
      for (Iterator> ei =
          map.entrySet().iterator(); ei.hasNext(); ) {
        Map.Entry e = ei.next();
        if (e.getValue() <= minCount) {
          ei.remove();
        }
      }
      if (map.size() > MAX_ELEMENTS) {
        minCount++;
      }
    }
    return minCount;
  }

  private static void appendTop(StringBuilder buff,
      HashMap map, int count, int total, boolean table) {
    for (int x = 0, min = 0; ; ) {
      int highest = 0;
      Map.Entry best = null;
      for (Map.Entry el : map.entrySet()) {
        if (el.getValue() > highest) {
          best = el;
          highest = el.getValue();
        }
      }
      if (best == null) {
        break;
      }
      map.remove(best.getKey());
      if (++x >= count) {
        if (best.getValue() < min) {
          break;
        }
        min = best.getValue();
      }
      int c = best.getValue();
      int percent = 100 * c / Math.max(total, 1);
      if (table) {
        if (percent > 1) {
          buff.append(percent).
              append("%: ").append(best.getKey()).
              append(LINE_SEPARATOR);
        }
      } else {
        buff.append(c).append('/').append(total).append(" (").
            append(percent).
            append("%):").append(LINE_SEPARATOR).
            append(best.getKey()).
            append(LINE_SEPARATOR);
      }
    }
  }

  private void run(String... args) {
    if (args.length == 0) {
      System.out.println("Show profiling data");
      System.out.println("Usage: java " + getClass().getName() +
          "  | ");
      System.out.println("Processes:");
      String processes = exec("jps", "-l");
      System.out.println(processes);
      return;
    }
    start = System.nanoTime();
    if (args[0].matches("\\d+")) {
      pid = Integer.parseInt(args[0]);
      long last = 0;
      while (true) {
        tick();
        long t = System.nanoTime();
        if (t - last > TimeUnit.SECONDS.toNanos(5)) {
          time = System.nanoTime() - start;
          System.out.println(getTopTraces(3));
          last = t;
        }
      }
    }
    try {
      for (String arg : args) {
        if (arg.startsWith("-")) {
          if ("-classes".equals(arg)) {
            sumClasses = true;
          } else if ("-methods".equals(arg)) {
            sumMethods = true;
          } else if ("-packages".equals(arg)) {
            sumClasses = false;
            sumMethods = false;
          } else {
            throw new IllegalArgumentException(arg);
          }
          continue;
        }
        String file = arg;
        Reader reader;
        LineNumberReader r;
        reader = new InputStreamReader(
            new FileInputStream(file), "CP1252");
        r = new LineNumberReader(reader);
        while (true) {
          String line = r.readLine();
          if (line == null) {
            break;
          } else if (line.startsWith("Full thread dump")) {
            threadDumps++;
          }
        }
        reader.close();
        reader = new InputStreamReader(
            new FileInputStream(file), "CP1252");
        r = new LineNumberReader(reader);
        processList(readStackTrace(r));
        reader.close();
      }
      System.out.println(getTopTraces(5));
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Start collecting profiling data.
   *
   * @return this
   */
  public Profiler startCollecting() {
    thread = new Thread(this, "Profiler");
    thread.setDaemon(true);
    thread.start();
    return this;
  }

  /**
   * Stop collecting.
   *
   * @return this
   */
  public Profiler stopCollecting() {
    stop = true;
    if (thread != null) {
      try {
        thread.join();
      } catch (InterruptedException e) {
        // ignore
      }
      thread = null;
    }
    return this;
  }

  @Override
  public void run() {
    start = System.nanoTime();
    while (!stop) {
      try {
        tick();
      } catch (Throwable t) {
        break;
      }
    }
    time = System.nanoTime() - start;
  }

  private void tick() {
    if (interval > 0) {
      if (paused) {
        return;
      }
      try {
        Thread.sleep(interval);
      } catch (Exception e) {
        // ignore
      }
    }

    List list;
    if (pid != 0) {
      list = readRunnableStackTraces(pid);
    } else {
      list = getRunnableStackTraces();
    }
    threadDumps++;
    processList(list);
  }

  private void processList(List list) {
    for (Object[] dump : list) {
      if (startsWithAny(dump[0].toString(), ignoreThreads)) {
        continue;
      }
      StringBuilder buff = new StringBuilder();
      // simple recursive calls are ignored
      String last = null;
      boolean packageCounts = false;
      for (int j = 0, i = 0; i < dump.length && j < depth; i++) {
        String el = dump[i].toString();
        if (!el.equals(last) && !startsWithAny(el, ignoreLines)) {
          last = el;
          buff.append("at ").append(el).append(LINE_SEPARATOR);
          if (!packageCounts && !startsWithAny(el, ignorePackages)) {
            packageCounts = true;
            int index = 0;
            for (; index < el.length(); index++) {
              char c = el.charAt(index);
              if (c == '(' || Character.isUpperCase(c)) {
                break;
              }
            }
            if (index > 0 && el.charAt(index - 1) == '.') {
              index--;
            }
            if (sumClasses) {
              int m = el.indexOf('.', index + 1);
              index = m >= 0 ? m : index;
            }
            if (sumMethods) {
              int m = el.indexOf('(', index + 1);
              index = m >= 0 ? m : index;
            }
            String groupName = el.substring(0, index);
            increment(summary, groupName, 0);
          }
          j++;
        }
      }
      if (buff.length() > 0) {
        minCount = increment(counts, buff.toString().trim(), minCount);
        total++;
      }
    }
  }

  /**
   * Get the top stack traces.
   *
   * @param count the maximum number of stack traces
   * @return the stack traces.
   */
  public String getTop(int count) {
    stopCollecting();
    return getTopTraces(count);
  }

  private String getTopTraces(int count) {
    StringBuilder buff = new StringBuilder();
    buff.append("Profiler: top ").append(count).append(" stack trace(s) of ");
    if (time > 0) {
      buff.append(" of ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms");
    }
    if (threadDumps > 0) {
      buff.append(" of ").append(threadDumps).append(" thread dumps");
    }
    buff.append(":").append(LINE_SEPARATOR);
    if (counts.size() == 0) {
      buff.append("(none)").append(LINE_SEPARATOR);
    }
    HashMap copy = new HashMap(counts);
    appendTop(buff, copy, count, total, false);
    buff.append("summary:").append(LINE_SEPARATOR);
    copy = new HashMap(summary);
    appendTop(buff, copy, count, total, true);
    buff.append('.');
    return buff.toString();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy