You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2013/09/30 15:34:14 UTC

svn commit: r1527555 - /jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java

Author: thomasm
Date: Mon Sep 30 13:34:14 2013
New Revision: 1527555

URL: http://svn.apache.org/r1527555
Log:
OAK-641: Improved benchmark tooling - add a standalone profiler

Added:
    jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java

Added: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java?rev=1527555&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java (added)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/util/Profiler.java Mon Sep 30 13:34:14 2013
@@ -0,0 +1,442 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.benchmark.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.lang.instrument.Instrumentation;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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).
+ */
+public class Profiler implements Runnable {
+
+    private static Instrumentation instrumentation;
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");
+    private static final int MAX_ELEMENTS = 1000;
+
+    public int interval = 2;
+    public int depth = 48;
+    public boolean paused;
+    public boolean sumClasses;
+
+    private int pid;
+
+    private final String[] ignoreLines = {};
+    private final String[] ignorePackages = (
+            "java," +
+            "sun," +
+            "com.sun."
+            ).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.PlainSocketImpl.accept," +
+            "java.net.PlainSocketImpl.socketAccept," +
+            "java.net.SocketInputStream.socketRead," +
+            "java.net.SocketOutputStream.socketWrite," +
+            "sun.awt.windows.WToolkit.eventLoop," +
+            "sun.misc.Unsafe.park," +
+            "dalvik.system.VMStack.getThreadStackTrace," +
+            "dalvik.system.NativeStart.run"
+            ).split(",");
+
+    private volatile boolean stop;
+    private final HashMap<String, Integer> counts = new HashMap<String, Integer>();
+
+    /**
+     * The summary (usually one entry per package, unless sumClasses is enabled,
+     * in which case it's one entry per class).
+     */
+    private final HashMap<String, Integer> summary = new HashMap<String, Integer>();
+    private int minCount = 1;
+    private int total;
+    private Thread thread;
+    private long start;
+    private long time;
+
+    /**
+     * 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 void run(String... args) {
+        if (args.length == 0) {
+            System.out.println("Show profiling data");
+            System.out.println("Usage: java " + getClass().getName() + " <pid>");
+            System.out.println("Processes:");
+            String processes = exec("jps", "-l");
+            System.out.println(processes);
+            return;
+        }
+        pid = Integer.parseInt(args[0]);
+        start = System.currentTimeMillis();
+        long last = 0;
+        while (true) {
+            tick();
+            long t = System.currentTimeMillis();
+            if (t - last > 5000) {
+                time = System.currentTimeMillis() - start;
+                System.out.println(getTopTraces(3));
+                last = t;
+            }
+        }
+    }
+
+    private static List<Object[]> getRunnableStackTraces() {
+        ArrayList<Object[]> list = new ArrayList<Object[]>();
+        Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
+        for (Map.Entry<Thread, StackTraceElement[]> 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<Object[]> readRunnableStackTraces(int pid) {
+        ArrayList<Object[]> list = new ArrayList<Object[]>();
+        try {
+            String jstack = exec("jstack", "" + pid);
+            LineNumberReader r = new LineNumberReader(new StringReader(jstack));
+            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<String> stack = new ArrayList<String>();
+                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;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    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);
+                }
+            }
+        }.run();
+    }
+
+    /**
+     * 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.currentTimeMillis();
+        while (!stop) {
+            try {
+                tick();
+            } catch (Throwable t) {
+                break;
+            }
+        }
+        time = System.currentTimeMillis() - start;
+    }
+
+    private void tick() {
+        if (interval > 0) {
+            if (paused) {
+                return;
+            }
+            try {
+                Thread.sleep(interval);
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        List<Object[]> list;
+        if (pid != 0) {
+            list = readRunnableStackTraces(pid);
+        } else {
+            list = getRunnableStackTraces();
+        }
+        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;
+                        }
+                        String groupName = el.substring(0, index);
+                        increment(summary, groupName, 0);
+                    }
+                    j++;
+                }
+            }
+            if (buff.length() > 0) {
+                minCount = increment(counts, buff.toString().trim(), minCount);
+                total++;
+            }
+        }
+    }
+
+    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<String, Integer> 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<Map.Entry<String, Integer>> ei = map.entrySet().iterator(); ei.hasNext();) {
+                Map.Entry<String, Integer> e = ei.next();
+                if (e.getValue() <= minCount) {
+                    ei.remove();
+                }
+            }
+            if (map.size() > MAX_ELEMENTS) {
+                minCount++;
+            }
+        }
+        return minCount;
+    }
+
+    /**
+     * 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 ").append(time).
+            append(" ms:").append(LINE_SEPARATOR);
+        if (counts.size() == 0) {
+            buff.append("(none)").append(LINE_SEPARATOR);
+        }
+        HashMap<String, Integer> copy = new HashMap<String, Integer>(counts);
+        appendTop(buff, copy, count, total, false);
+        buff.append("summary:").append(LINE_SEPARATOR);
+        copy = new HashMap<String, Integer>(summary);
+        appendTop(buff, copy, count, total, true);
+        buff.append('.');
+        return buff.toString();
+    }
+
+    private static void appendTop(StringBuilder buff, HashMap<String, Integer> map, int count, int total, boolean table) {
+        for (int x = 0, min = 0;;) {
+            int highest = 0;
+            Map.Entry<String, Integer> best = null;
+            for (Map.Entry<String, Integer> 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);
+            }
+        }
+    }
+
+}