package org.araqne.logdb.metadata; 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.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.araqne.logdb.FieldOrdering; import org.araqne.logdb.FunctionRegistry; import org.araqne.logdb.MetadataCallback; import org.araqne.logdb.MetadataProvider; import org.araqne.logdb.MetadataService; import org.araqne.logdb.QueryContext; import org.araqne.logdb.QueryParseException; import org.araqne.logdb.Row; import org.araqne.logdb.query.parser.CommandOptions; import org.araqne.logdb.query.parser.ParseResult; import org.araqne.logdb.query.parser.QueryTokenizer; @Component(name = "logdb-thread-metadata") public class ThreadMetadataProvider implements MetadataProvider, FieldOrdering { @Requires private MetadataService metadataService; @Requires private FunctionRegistry functionRegistry; @Validate public void start() { metadataService.addProvider(this); } @Invalidate public void stop() { if (metadataService != null) metadataService.removeProvider(this); } @Override public String getType() { return "threads"; } @Override public List<String> getFieldOrder() { return Arrays.asList("tid", "name", "state", "stacktrace"); } @Override public void verify(QueryContext context, String queryString) { if (!context.getSession().isAdmin()) { throw new QueryParseException("95040", -1, -1, null); // throw new QueryParseException("no-read-permission", -1); } QueryTokenizer.parseOptions(context, queryString, 0, Arrays.asList("prettystack", "stack"), functionRegistry); } @SuppressWarnings("unchecked") @Override public void query(QueryContext context, String queryString, MetadataCallback callback) { ParseResult r = QueryTokenizer.parseOptions(context, queryString, 0, Arrays.asList("prettystack", "stack"), functionRegistry); Map<String, Object> options = (Map<String, Object>) r.value; // enable by default boolean prettyStack = true; if (options.get("prettystack") != null) { prettyStack = CommandOptions.parseBoolean(options.get("prettystack").toString()); } boolean stack = true; if (options.get("stack") != null) { stack = CommandOptions.parseBoolean(options.get("stack").toString()); } if (stack) { for (int i = 0; i < 3; i++) { try { dumpThreads(callback, prettyStack); break; } catch (NullPointerException e) { // ThreadInfo can throw NPE, retry is required } } } else { enumerateThreads(callback); } } private void enumerateThreads(MetadataCallback callback) { ThreadGroup rootGroup = Thread.currentThread( ).getThreadGroup( ); ThreadGroup parentGroup; while ( ( parentGroup = rootGroup.getParent() ) != null ) { rootGroup = parentGroup; } Thread[] threads = new Thread[ rootGroup.activeCount() ]; while ( rootGroup.enumerate( threads, true ) == threads.length ) { threads = new Thread[ threads.length * 2 ]; } for (Thread t: threads) { if (t == null || !t.isAlive()) continue; if (callback.isCancelled()) break; Map<String, Object> m = new HashMap<String, Object>(); m.put("tid", t.getId()); m.put("name", t.getName()); m.put("state", t.getState().toString()); callback.onPush(new Row(m)); } } private void dumpThreads(MetadataCallback callback, boolean prettyStack) { ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] tids = bean.getAllThreadIds(); for (ThreadInfo t : bean.getThreadInfo(tids, true, true)) { if (t == null) continue; if (callback.isCancelled()) break; Map<String, Object> m = new HashMap<String, Object>(); m.put("tid", t.getThreadId()); m.put("name", t.getThreadName()); m.put("state", t.getThreadState().toString()); m.put("stacktrace", prettyStack ? mergeStackTrace(t) : convertStackTrace(t.getStackTrace())); callback.onPush(new Row(m)); } } private String mergeStackTrace(ThreadInfo t) { StackTraceElement[] stacktrace = t.getStackTrace(); MonitorInfo[] monitors = t.getLockedMonitors(); int idx = 0; StringBuilder sb = new StringBuilder(); for (StackTraceElement el : stacktrace) { LockInfo lock = t.getLockInfo(); String lockOwner = t.getLockOwnerName(); sb.append(String.format("%s.%s%s\n", el.getClassName(), el.getMethodName(), getFileAndLineNumber(el))); if (idx == 0) { if (el.getClassName().equals("java.lang.Object") && el.getMethodName().equals("wait")) { if (lock != null) { sb.append(String.format("- waiting on <0x%016x> (%s)\n", lock.getIdentityHashCode(), lock.getClassName())); } } else if (lock != null) { if (lockOwner == null) { sb.append(String.format("- parking to wait for <0x%016x> (%s)\n", lock.getIdentityHashCode(), lock.getClassName())); } else { sb.append(String.format("- waiting to lock <0x%016x> (%s) owned by \"%s\" @%d\n", lock.getIdentityHashCode(), lock.getClassName(), lockOwner, t.getLockOwnerId())); } } } for (MonitorInfo monitor : monitors) { if (monitor.getLockedStackDepth() == idx) { sb.append(String.format("- locked <0x%016x> (%s)\n", monitor.getIdentityHashCode(), monitor.getClassName())); } } idx++; } return sb.toString(); } private String getFileAndLineNumber(StackTraceElement el) { if (el.getFileName() != null && el.getLineNumber() > 0) return String.format(" (%s:%d)", el.getFileName(), el.getLineNumber()); else if (el.getFileName() != null && el.getLineNumber() <= 0) return String.format(" (%s)", el.getFileName()); else return ""; } private List<Object> convertStackTrace(StackTraceElement[] stacktrace) { List<Object> l = new ArrayList<Object>(); if (stacktrace == null) return null; for (StackTraceElement e : stacktrace) { Map<String, Object> m = new HashMap<String, Object>(); m.put("class", e.getClassName()); m.put("file", e.getFileName()); m.put("line", e.getLineNumber()); m.put("method", e.getMethodName()); l.add(m); } return l; } private static class ThreadOrder implements Comparator<Thread> { @Override public int compare(Thread o1, Thread o2) { return (int) (o1.getId() - o2.getId()); } } }