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