Updated Branches:
  refs/heads/master b9a1ff7cc -> d75f6f0d5

[KARAF-2626] Add load-test and improve threads commands


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/b2cbfb5c
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/b2cbfb5c
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/b2cbfb5c

Branch: refs/heads/master
Commit: b2cbfb5cd71838786245728b70083193bb4e0e65
Parents: b9a1ff7
Author: Guillaume Nodet <gno...@gmail.com>
Authored: Tue Dec 17 09:24:15 2013 +0100
Committer: Guillaume Nodet <gno...@gmail.com>
Committed: Tue Dec 17 17:49:00 2013 +0100

----------------------------------------------------------------------
 .../apache/karaf/bundle/command/LoadTest.java   | 173 +++++++++++
 .../OSGI-INF/blueprint/shell-bundles.xml        |   4 +
 .../shell/commands/impl/ThreadsAction.java      | 291 +++++++++++++++++--
 .../karaf/shell/commands/impl/ThreadsTest.java  |  42 ++-
 4 files changed, 479 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/b2cbfb5c/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
----------------------------------------------------------------------
diff --git 
a/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java 
b/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
new file mode 100644
index 0000000..fe36d52
--- /dev/null
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.karaf.bundle.command;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jline.console.ConsoleReader;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+@Command(scope = "bundle", name = "load-test", description = "Load test bundle 
lifecycle")
+public class LoadTest extends OsgiCommandSupport {
+
+    @Option(name = "--threads", description = "number of concurrent threads")
+    int threads = 2;
+
+    @Option(name = "--delay", description = "maximum delay between actions")
+    int delay = 1;
+
+    @Option(name = "--iterations", description = "number of iterations per 
thread")
+    int iterations = 100;
+
+    @Option(name = "--refresh", description = "percentage of bundle refresh vs 
restart")
+    int refresh = 20;
+
+    @Option(name = "--excludes", description = "List of bundles (ids or 
symbolic names) to exclude")
+    List<String> excludes = Arrays.asList("0", "org.ops4j.pax.url.mvn", 
"org.ops4j.pax.logging.pax-logging-api", 
"org.ops4j.pax.logging.pax-logging-service");
+
+    @Override
+    protected Object doExecute() throws Exception {
+        if (!confirm(session)) {
+            return null;
+        }
+        final BundleContext bundleContext = 
getBundleContext().getBundle(0).getBundleContext();
+        final FrameworkWiring wiring = 
bundleContext.getBundle().adapt(FrameworkWiring.class);
+        final CountDownLatch latch = new CountDownLatch(threads);
+        final Bundle[] bundles = bundleContext.getBundles();
+        final AtomicBoolean[] locks = new AtomicBoolean[bundles.length];
+        for (int b = 0; b < locks.length; b++) {
+            locks[b] = new AtomicBoolean(true);
+            // Avoid touching excluded bundles
+            if (excludes.contains(Long.toString(bundles[b].getBundleId()))
+                    || excludes.contains(bundles[b].getSymbolicName())) {
+                continue;
+            }
+            // Only touch active bundles
+            if (bundles[b].getState() != Bundle.ACTIVE) {
+                continue;
+            }
+            // Now set the lock to available
+            locks[b].set(false);
+        }
+        for (int i = 0; i < threads; i++) {
+            new Thread() {
+                public void run() {
+                    try {
+                        Random rand = new Random();
+                        for (int j = 0; j < iterations; j++) {
+                            for (;;) {
+                                int b = rand.nextInt(bundles.length);
+                                if (locks[b].compareAndSet(false, true)) {
+                                    try {
+                                        // Only touch active bundles
+                                        if (bundles[b].getState() != 
Bundle.ACTIVE) {
+                                            continue;
+                                        }
+                                        if (rand.nextInt(100) < refresh) {
+                                            try {
+                                                bundles[b].update();
+                                                final CountDownLatch latch = 
new CountDownLatch(1);
+                                                
wiring.refreshBundles(Collections.singletonList(bundles[b]), new 
FrameworkListener() {
+                                                    public void 
frameworkEvent(FrameworkEvent event) {
+                                                        latch.countDown();
+                                                    }
+                                                });
+                                                latch.await();
+                                            } finally {
+                                                while (true) {
+                                                    try {
+                                                        
bundles[b].start(Bundle.START_TRANSIENT);
+                                                        break;
+                                                    } catch (Exception e) {
+                                                        Thread.sleep(1);
+                                                    }
+                                                }
+                                            }
+                                        } else {
+                                            try {
+                                                
bundles[b].stop(Bundle.STOP_TRANSIENT);
+                                            } finally {
+                                                while (true) {
+                                                    try {
+                                                        
bundles[b].start(Bundle.START_TRANSIENT);
+                                                        break;
+                                                    } catch (Exception e) {
+                                                        Thread.sleep(1);
+                                                    }
+                                                }
+                                            }
+                                        }
+                                        Thread.sleep(rand.nextInt(delay));
+                                    } catch (Exception e) {
+                                        e.printStackTrace();
+                                    } finally {
+                                        locks[b].set(false);
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    } catch (Throwable t) {
+                        t.printStackTrace();
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+            }.start();
+        }
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    latch.await();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                System.err.println("Load test finished");
+            }
+        }.start();
+        return null;
+    }
+
+    private boolean confirm(CommandSession session) throws IOException {
+        for (;;) {
+            ConsoleReader reader = (ConsoleReader) 
session.get(".jline.reader");
+            String msg = "You are about to perform a start/stop/refresh load 
test on bundles.\nDo you wish to continue (yes/no): ";
+            String str = reader.readLine(msg);
+            if ("yes".equalsIgnoreCase(str)) {
+                return true;
+            }
+            if ("no".equalsIgnoreCase(str)) {
+                return false;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/b2cbfb5c/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
----------------------------------------------------------------------
diff --git 
a/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml 
b/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
index 91673f9..bed5c0f 100644
--- a/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
+++ b/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
@@ -128,6 +128,10 @@
                 <property name="bundleWatcher" ref="bundleWatcher"/>
             </action>
         </command>
+        <command>
+            <action class="org.apache.karaf.bundle.command.LoadTest" >
+            </action>
+        </command>
     </command-bundle>
     
 </blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/b2cbfb5c/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
----------------------------------------------------------------------
diff --git 
a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
 
b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
index 3f6465b..52cb76e 100644
--- 
a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
+++ 
b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
@@ -16,9 +16,16 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
+import java.lang.management.LockInfo;
 import java.lang.management.ManagementFactory;
+import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
 import java.lang.management.ThreadMXBean;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
@@ -33,47 +40,283 @@ import org.apache.karaf.shell.table.ShellTable;
 @Command(scope = "shell", name = "threads", description = "Prints the current 
threads (optionally with stacktraces)")
 public class ThreadsAction extends AbstractAction {
 
+    @Option(name = "--tree" , description = "Display threads as a tree")
+    boolean tree = false;
+
+    @Option(name = "--list" , description = "Display threads as a list")
+    boolean list = false;
+
+    @Option(name = "-e", aliases = { "--empty-groups" }, description = "Show 
empty groups")
+    boolean empty = false;
+
+    @Option(name = "-t", aliases = { "--threshold" }, description = "Minimal 
number of interesting stack trace line to display a thread")
+    int threshold = 1;
+
+    @Option(name = "--locks", description = "Display locks")
+    boolean locks = false;
+
+    @Option(name = "--monitors", description = "Display monitors")
+    boolean monitors = false;
+
+    @Option(name = "--packages", description = "Pruned packages")
+    List<String> packages = Arrays.asList("java.", "sun.");
+
     @Argument(name = "id", description="Show details for thread with this Id", 
required = false, multiValued = false)
     Long id;
 
     @Option(name = "--no-format", description = "Disable table rendered 
output", required = false, multiValued = false)
     boolean noFormat;
 
+    @Override
     protected Object doExecute() throws Exception {
+        Map<Long, ThreadInfo> threadInfos = new TreeMap<Long, ThreadInfo>();
         ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
-        
+        ThreadInfo[] infos;
+        if (threadsBean.isObjectMonitorUsageSupported() && 
threadsBean.isSynchronizerUsageSupported()) {
+            infos = threadsBean.dumpAllThreads(true, true);
+        } else {
+            infos = threadsBean.getThreadInfo(threadsBean.getAllThreadIds(), 
Integer.MAX_VALUE);
+        }
+        for (ThreadInfo info : infos) {
+            threadInfos.put(info.getThreadId(), info);
+        }
+
         if (id != null) {
-            printThread(threadsBean, id);
+            ThreadInfo ti = threadInfos.get(id);
+            if (ti != null) {
+                System.out.println("Thread " + ti.getThreadId() + " " + 
ti.getThreadName() + " " + ti.getThreadState());
+                System.out.println("Stacktrace:");
+                StackTraceElement[] st = ti.getStackTrace();
+                for (StackTraceElement ste : st) {
+                    System.out.println(ste.getClassName() + "." + 
ste.getMethodName() + " line: " + ste.getLineNumber());
+                }
+            }
+        } else if (list) {
+            ShellTable table = new ShellTable();
+            table.column("Id");
+            table.column("Name");
+            table.column("State");
+            table.column("CPU time");
+            table.column("Usr time");
+            for (ThreadInfo thread : threadInfos.values()) {
+                long id = thread.getThreadId();
+                table.addRow().addContent(
+                        id,
+                        thread.getThreadName(),
+                        thread.getThreadState(),
+                        threadsBean.getThreadCpuTime(id) / 1000000,
+                        threadsBean.getThreadUserTime(id) / 1000000);
+            }
+            table.print(System.out, !noFormat);
         } else {
-            printThreadList(threadsBean);
+            ThreadGroup group = Thread.currentThread().getThreadGroup();
+            while (group.getParent() != null) {
+                group = group.getParent();
+            }
+            ThreadGroupData data = new ThreadGroupData(group, threadInfos);
+            data.print();
         }
+
         return null;
     }
 
-    void printThread(ThreadMXBean threadsBean, Long id) {
-        threadsBean.setThreadCpuTimeEnabled(true);
-        ThreadInfo ti = threadsBean.getThreadInfo(id, Integer.MAX_VALUE);
-        System.out.println("Thread " + ti.getThreadId() + " " + 
ti.getThreadName() + " " + ti.getThreadState());
-        System.out.println("Stacktrace:");
-        StackTraceElement[] st = ti.getStackTrace();
-        for (StackTraceElement ste : st) {
-            System.out.println(ste.getClassName() + "." + ste.getMethodName() 
+ " line: " + ste.getLineNumber());
+    public class ThreadGroupData {
+        private final ThreadGroup group;
+        private final List<ThreadGroupData> groups;
+        private final List<ThreadData> threads;
+
+        public ThreadGroupData(ThreadGroup group, Map<Long, ThreadInfo> infos) 
{
+            this.group = group;
+            int nbGroups;
+            int nbThreads;
+            ThreadGroup[] childGroups = new ThreadGroup[32];
+            while (true) {
+                nbGroups = group.enumerate(childGroups, false);
+                if (nbGroups == childGroups.length) {
+                    childGroups = new ThreadGroup[childGroups.length * 2];
+                } else {
+                    break;
+                }
+            }
+            groups = new ArrayList<ThreadGroupData>();
+            for (ThreadGroup tg : childGroups) {
+                if (tg != null) {
+                    groups.add(new ThreadGroupData(tg, infos));
+                }
+            }
+            Thread[] childThreads = new Thread[32];
+            while (true) {
+                nbThreads = group.enumerate(childThreads, false);
+                if (nbThreads == childThreads.length) {
+                    childThreads = new Thread[childThreads.length * 2];
+                } else {
+                    break;
+                }
+            }
+            threads = new ArrayList<ThreadData>();
+            for (Thread t : childThreads) {
+                if (t != null) {
+                    threads.add(new ThreadData(t, infos.get(t.getId())));
+                }
+            }
+        }
+
+        public void print() {
+            if (tree) {
+                printTree("");
+            } else {
+                printDump("");
+            }
+        }
+
+        private void printTree(String indent) {
+            if (empty || hasInterestingThreads()) {
+                System.out.println(indent + "Thread Group \"" + 
group.getName() + "\"");
+                for (ThreadGroupData tgd : groups) {
+                    tgd.printTree(indent + "    ");
+                }
+                for (ThreadData td : threads) {
+                    if (td.isInteresting()) {
+                        td.printTree(indent + "    ");
+                    }
+                }
+            }
+        }
+
+        private void printDump(String indent) {
+            if (empty || hasInterestingThreads()) {
+                for (ThreadGroupData tgd : groups) {
+                    tgd.printDump(indent);
+                }
+                for (ThreadData td : threads) {
+                    if (td.isInteresting()) {
+                        td.printDump(indent);
+                    }
+                }
+            }
+        }
+
+        public boolean hasInterestingThreads() {
+            for (ThreadData td : threads) {
+                if (td.isInteresting()) {
+                    return true;
+                }
+            }
+            for (ThreadGroupData tgd : groups) {
+                if (tgd.hasInterestingThreads()) {
+                    return true;
+                }
+            }
+            return false;
         }
     }
 
-    void printThreadList(ThreadMXBean threadsBean) {
-        ThreadInfo[] threadInfoAr = threadsBean.dumpAllThreads(false, false);
-        ShellTable table = new ShellTable();
-        table.column("Id");
-        table.column("Name");
-        table.column("State");
-        table.column("CPU time");
-        table.column("User time");
-        for (ThreadInfo ti : threadInfoAr) {
-            long id = ti.getThreadId();
-            table.addRow().addContent(id, ti.getThreadName(), 
ti.getThreadState(), threadsBean.getThreadCpuTime(id) / 1000000, 
threadsBean.getThreadUserTime(id) / 1000000);
-        }
-        table.print(System.out, !noFormat);
+    public class ThreadData {
+        private final Thread thread;
+        private ThreadInfo info;
+
+        public ThreadData(Thread thread, ThreadInfo info) {
+            this.thread = thread;
+            this.info = info;
+        }
+
+        public void printTree(String indent) {
+            System.out.println(indent + "    " + "\"" + thread.getName() + 
"\": " + thread.getState());
+        }
+
+        public void printDump(String indent) {
+            if (info != null && isInteresting()) {
+                printThreadInfo("    ");
+                if (locks) {
+                    printLockInfo("    ");
+                }
+                if (monitors) {
+                    printMonitorInfo("    ");
+                }
+            }
+        }
+
+        public boolean isInteresting() {
+            int nb = 0;
+            StackTraceElement[] stacktrace = info.getStackTrace();
+            for (int i = 0; i < stacktrace.length; i++) {
+                StackTraceElement ste = stacktrace[i];
+                boolean interestingLine = true;
+                for (String pkg : packages) {
+                    if (ste.getClassName().startsWith(pkg)) {
+                        interestingLine = false;
+                        break;
+                    }
+                }
+                if (interestingLine) {
+                    nb++;
+                }
+            }
+            return nb >= threshold;
+        }
+
+        private void printThreadInfo(String indent) {
+            // print thread information
+            printThread(indent);
+
+            // print stack trace with locks
+            StackTraceElement[] stacktrace = info.getStackTrace();
+            MonitorInfo[] monitors = info.getLockedMonitors();
+            for (int i = 0; i < stacktrace.length; i++) {
+                StackTraceElement ste = stacktrace[i];
+                System.out.println(indent + "at " + ste.toString());
+                for (MonitorInfo mi : monitors) {
+                    if (mi.getLockedStackDepth() == i) {
+                        System.out.println(indent + "  - locked " + mi);
+                    }
+                }
+            }
+            System.out.println();
+        }
+
+        private void printThread(String indent) {
+            StringBuilder sb = new StringBuilder("\"" + info.getThreadName() + 
"\"" + " Id="
+                    + info.getThreadId() + " in " + info.getThreadState());
+            if (info.getLockName() != null) {
+                sb.append(" on lock=" + info.getLockName());
+            }
+            if (info.isSuspended()) {
+                sb.append(" (suspended)");
+            }
+            if (info.isInNative()) {
+                sb.append(" (running in native)");
+            }
+            System.out.println(sb.toString());
+            if (info.getLockOwnerName() != null) {
+                System.out.println(indent + " owned by " + 
info.getLockOwnerName() + " Id="
+                        + info.getLockOwnerId());
+            }
+        }
+
+        private void printMonitorInfo(String indent) {
+            MonitorInfo[] monitors = info.getLockedMonitors();
+            if (monitors != null && monitors.length > 0) {
+                System.out.println(indent + "Locked monitors: count = " + 
monitors.length);
+                for (MonitorInfo mi : monitors) {
+                    System.out.println(indent + "  - " + mi + " locked at ");
+                    System.out.println(indent + "      " + 
mi.getLockedStackDepth() + " "
+                            + mi.getLockedStackFrame());
+                }
+                System.out.println();
+            }
+        }
+
+        private void printLockInfo(String indent) {
+            LockInfo[] locks = info.getLockedSynchronizers();
+            if (locks != null && locks.length > 0) {
+                System.out.println(indent + "Locked synchronizers: count = " + 
locks.length);
+                for (LockInfo li : locks) {
+                    System.out.println(indent + "  - " + li);
+                }
+                System.out.println();
+            }
+        }
+
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/b2cbfb5c/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
----------------------------------------------------------------------
diff --git 
a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
 
b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
index 077f93b..5daf61c 100644
--- 
a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
+++ 
b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
@@ -1,8 +1,21 @@
+/*
+ * 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.karaf.shell.commands.impl;
 
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadMXBean;
-
 import org.junit.Test;
 
 /**
@@ -11,13 +24,28 @@ import org.junit.Test;
 public class ThreadsTest {
     @Test
     public void testThreadlist() throws Exception {
-        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
-        new ThreadsAction().printThreadList(threadMXBean);
+        ThreadsAction action = new ThreadsAction();
+        action.list = true;
+        action.doExecute();
     }
     
     @Test
     public void testThreadInfo() throws Exception {
-        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
-        new ThreadsAction().printThread(threadMXBean, 1L);
+        ThreadsAction action = new ThreadsAction();
+        action.id = 1L;
+        action.doExecute();
+    }
+
+    @Test
+    public void testThreadTree() throws Exception {
+        ThreadsAction action = new ThreadsAction();
+        action.tree = true;
+        action.doExecute();
+    }
+
+    @Test
+    public void testThreadDump() throws Exception {
+        ThreadsAction action = new ThreadsAction();
+        action.doExecute();
     }
 }

Reply via email to