Author: gnodet Date: Mon Dec 16 16:21:13 2013 New Revision: 1551253 URL: http://svn.apache.org/r1551253 Log: [KARAF-2626] Add load-test and threads commands
Added: karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/LoadTest.java karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/Threads.java Modified: karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/bundle.info Added: karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/LoadTest.java URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/LoadTest.java?rev=1551253&view=auto ============================================================================== --- karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/LoadTest.java (added) +++ karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/LoadTest.java Mon Dec 16 16:21:13 2013 @@ -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.shell.dev; + +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.gogo.commands.Command; +import org.apache.felix.gogo.commands.Option; +import org.apache.felix.service.command.CommandSession; +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 = "dev", 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; + } + } + } +} Added: karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/Threads.java URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/Threads.java?rev=1551253&view=auto ============================================================================== --- karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/Threads.java (added) +++ karaf/branches/karaf-2.x/shell/dev/src/main/java/org/apache/karaf/shell/dev/Threads.java Mon Dec 16 16:21:13 2013 @@ -0,0 +1,318 @@ +/* + * 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.dev; + +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.felix.gogo.commands.Argument; +import org.apache.felix.gogo.commands.Command; +import org.apache.felix.gogo.commands.Option; +import org.apache.karaf.shell.console.OsgiCommandSupport; + +@Command(scope = "dev", name = "threads", description = "Show threads in the JVM.") +public class Threads extends OsgiCommandSupport { + + @Option(name = "--tree" , description = "Display threads as a tree") + private boolean tree = false; + + @Option(name = "--list" , description = "Display threads as a list") + private boolean list = false; + + @Option(name = "-e", aliases = { "--empty-groups" }, description = "Show empty groups") + private boolean empty = false; + + @Option(name = "-t", aliases = { "--threshold" }, description = "Minimal number of interesting stack trace line to display a thread") + private int threshold = 1; + + @Option(name = "--locks", description = "Display locks") + private boolean locks = false; + + @Option(name = "--monitors", description = "Display monitors") + private boolean monitors = false; + + @Option(name = "--packages", description = "Pruned packages") + private List<String> packages = Arrays.asList("java.", "sun."); + + @Argument(name = "id", description = "Show details for thread with this Id", required = false, multiValued = false) + private Long id; + + @Override + protected Object doExecute() throws Exception { + Map<Long, ThreadInfo> threadInfos = new TreeMap<Long, ThreadInfo>(); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] infos; + if (threadMXBean.isObjectMonitorUsageSupported() && threadMXBean.isSynchronizerUsageSupported()) { + infos = threadMXBean.dumpAllThreads(true, true); + } else { + infos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), Integer.MAX_VALUE); + } + for (ThreadInfo info : infos) { + threadInfos.put(info.getThreadId(), info); + } + + if (id != null) { + 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) { + System.out.println(" ID State CPU time Usr Time Name"); + for (ThreadInfo thread : threadInfos.values()) { + String id = String.valueOf(thread.getThreadId()); + while (id.length() < 4) { + id = " " + id; + } + String state = thread.getThreadState().toString(); + while (state.length() < 13) { + state = state + " "; + } + String cpu = String.valueOf(threadMXBean.getThreadCpuTime(thread.getThreadId()) / 1000000); + while (cpu.length() < 8) { + cpu = " " + cpu; + } + String user = String.valueOf(threadMXBean.getThreadUserTime(thread.getThreadId()) / 1000000); + while (user.length() < 8) { + user = " " + user; + } + System.out.println("[" + id + "] [" + state + "] [" + cpu + "] [" + user + "] " + thread.getThreadName()); + } + } else { + ThreadGroup group = Thread.currentThread().getThreadGroup(); + while (group.getParent() != null) { + group = group.getParent(); + } + ThreadGroupData data = new ThreadGroupData(group, threadInfos); + data.print(); + } + + return null; + } + + 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; + } + } + + 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(); + } + } + + } + +} Modified: karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml?rev=1551253&r1=1551252&r2=1551253&view=diff ============================================================================== --- karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml (original) +++ karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml Mon Dec 16 16:21:13 2013 @@ -46,6 +46,12 @@ <property name="watcher" ref="watcher"/> </action> </command> + <command name="dev/load-test"> + <action class="org.apache.karaf.shell.dev.LoadTest" /> + </command> + <command name="dev/threads"> + <action class="org.apache.karaf.shell.dev.Threads" /> + </command> </command-bundle> <!-- Referenace to the Configuration Admin Service --> Modified: karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/bundle.info URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/bundle.info?rev=1551253&r1=1551252&r2=1551253&view=diff ============================================================================== --- karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/bundle.info (original) +++ karaf/branches/karaf-2.x/shell/dev/src/main/resources/OSGI-INF/bundle.info Mon Dec 16 16:21:13 2013 @@ -19,6 +19,8 @@ \u001B[36mdev:restart\u001B[0m Restart Karaf. \u001B[36mdev:show-tree\u001B[0m Shows the tree of bundles based on the wiring information. \u001B[36mdev:watch\u001B[0m Watches and updates bundles. + \u001B[36mdev:load-test\u001B[0m Load test bundle lifecycle. + \u001B[36mdev:threads\u001B[0m Show threads in the JVM. \u001B[1mSEE ALSO\u001B[0m \u001B[36mCommands\u001B[0m section of the Karaf User Guide. \ No newline at end of file