/** * ThreaddumpService * Copyright 03.07.2015 by Michael Peter Christen, @0rb1t3r * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package net.yacy.grid.mcp.api.info; import javax.servlet.http.HttpServletResponse; import net.yacy.grid.http.APIHandler; import net.yacy.grid.http.ObjectAPIHandler; import net.yacy.grid.http.Query; import net.yacy.grid.http.ServiceResponse; import net.yacy.grid.tools.Memory; import java.lang.Thread.State; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.DecimalFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; /** * The Threadump Service * call http://localhost:8100/yacy/grid/mcp/info/threaddump.txt */ public class ThreaddumpService extends ObjectAPIHandler implements APIHandler { private static final long serialVersionUID = -7095346222464124198L; private static final long startupTime = System.currentTimeMillis(); private static final String multiDumpFilter = ".*((java.net.DatagramSocket.receive)|(java.lang.Thread.getAllStackTraces)|(java.net.SocketInputStream.read)|(java.net.ServerSocket.accept)|(java.net.Socket.connect)).*"; private static final Pattern multiDumpFilterPattern = Pattern.compile(multiDumpFilter); public static final String NAME = "threaddump"; private static final Thread.State[] ORDERED_STATES = new Thread.State[]{ Thread.State.BLOCKED, Thread.State.RUNNABLE, Thread.State.TIMED_WAITING, Thread.State.WAITING, Thread.State.NEW, Thread.State.TERMINATED}; @Override public String getAPIPath() { return "/yacy/grid/mcp/info/" + NAME + ".txt"; } @Override public ServiceResponse serviceImpl(Query post, HttpServletResponse response) { int multi = post.get("multi", post.get("count", 0)); final StringBuilder buffer = new StringBuilder(1000); // Thread dump final Date dt = new Date(); int keylen = 30; bufferappend(buffer, "************* Start Thread Dump " + dt + " *******************"); bufferappend(buffer, ""); bufferappend(buffer, keylen, "Assigned Memory", Memory.assigned()); bufferappend(buffer, keylen, "Used Memory", Memory.used()); bufferappend(buffer, keylen, "Available Memory", Memory.available()); bufferappend(buffer, keylen, "Short Status", Memory.shortStatus() ? "true" : "false"); bufferappend(buffer, keylen, "Short Threshold", Float.toString(Memory.shortmemthreshold)); bufferappend(buffer, keylen, "Cores", Memory.cores()); bufferappend(buffer, keylen, "Active Thread Count", Thread.activeCount()); bufferappend(buffer, keylen, "Total Started Thread Count", Memory.threadBean.getTotalStartedThreadCount()); bufferappend(buffer, keylen, "Peak Thread Count", Memory.threadBean.getPeakThreadCount()); bufferappend(buffer, keylen, "System Load Average", Memory.osBean.getSystemLoadAverage()); long runtimeseconds = (System.currentTimeMillis() - startupTime) / 1000; int runtimeminutes = (int) (runtimeseconds / 60); runtimeseconds = runtimeseconds % 60; int runtimehours = runtimeminutes / 60; runtimeminutes = runtimeminutes % 60; bufferappend(buffer, keylen, "Runtime", runtimehours + "h " + runtimeminutes + "m " + runtimeseconds + "s"); // print system beans for (Method method : Memory.osBean.getClass().getDeclaredMethods()) try { method.setAccessible(true); if (method.getName().startsWith("get") && Modifier.isPublic(method.getModifiers())) { bufferappend(buffer, keylen, method.getName(), method.invoke(Memory.osBean)); } } catch (Throwable e) {} bufferappend(buffer, ""); bufferappend(buffer, ""); if (multi > 0) { // generate multiple dumps final Map<String, Integer> dumps = new HashMap<String, Integer>(); for (int i = 0; i < multi; i++) { try { ThreadDump dump = new ThreadDump(ThreadDump.getAllStackTraces(), Thread.State.RUNNABLE); for (final Map.Entry<StackTrace, SortedSet<String>> e: dump.entrySet()) { if (multiDumpFilterPattern.matcher(e.getKey().text).matches()) continue; Integer c = dumps.get(e.getKey().text); if (c == null) dumps.put(e.getKey().text, Integer.valueOf(e.getValue().size())); else { c = Integer.valueOf(c.intValue() + e.getValue().size()); dumps.put(e.getKey().text, c); } } } catch (final OutOfMemoryError e) { break; } } // write dumps while (!dumps.isEmpty()) { final Map.Entry<String, Integer> e = removeMax(dumps); bufferappend(buffer, "Occurrences: " + e.getValue()); bufferappend(buffer, e.getKey()); bufferappend(buffer, ""); } bufferappend(buffer, ""); } else { // generate a single thread dump final Map<Thread, StackTraceElement[]> stackTraces = ThreadDump.getAllStackTraces(); // write those ordered into the stackTrace list for (Thread.State state: ORDERED_STATES) new ThreadDump(stackTraces, state).appendStackTraces(buffer, state); } ThreadMXBean threadbean = ManagementFactory.getThreadMXBean(); bufferappend(buffer, ""); bufferappend(buffer, "THREAD LIST FROM ThreadMXBean, " + threadbean.getThreadCount() + " threads:"); bufferappend(buffer, ""); ThreadInfo[] threadinfo = threadbean.dumpAllThreads(true, true); for (ThreadInfo ti: threadinfo) { bufferappend(buffer, ti.getThreadName()); } return new ServiceResponse(buffer.toString()); } private static class StackTrace { private String text; private StackTrace(final String text) { this.text = text; } @Override public boolean equals(final Object a) { return (a != null && a instanceof StackTrace && this.text.equals(((StackTrace) a).text)); } @Override public int hashCode() { return this.text.hashCode(); } @Override public String toString() { return this.text; } } private static Map.Entry<String, Integer> removeMax(final Map<String, Integer> result) { Map.Entry<String, Integer> max = null; for (final Map.Entry<String, Integer> e: result.entrySet()) { if (max == null || e.getValue().intValue() > max.getValue().intValue()) { max = e; } } result.remove(max.getKey()); return max; } private static void bufferappend(final StringBuilder buffer, int keylen, final String key, Object value) { if (value instanceof Double) bufferappend(buffer, keylen, key, ((Double) value).toString()); else if (value instanceof Number) bufferappend(buffer, keylen, key, ((Number) value).longValue()); else bufferappend(buffer, keylen, key, value.toString()); } private static final DecimalFormat cardinalFormatter = new DecimalFormat("###,###,###,###,###"); private static void bufferappend(final StringBuilder buffer, int keylen, final String key, long value) { bufferappend(buffer, keylen, key, cardinalFormatter.format(value)); } private static void bufferappend(final StringBuilder buffer, int keylen, final String key, String value) { String a = key; while (a.length() < keylen) a += " "; a += "="; for (int i = value.length(); i < 20; i++) a += " "; a += value; bufferappend(buffer, a); } private static void bufferappend(final StringBuilder buffer, final String a) { buffer.append(a); buffer.append('\n'); } private static class ThreadDump extends HashMap<StackTrace, SortedSet<String>> implements Map<StackTrace, SortedSet<String>> { private static final long serialVersionUID = -5587850671040354397L; private static Map<Thread, StackTraceElement[]> getAllStackTraces() { return Thread.getAllStackTraces(); } private ThreadDump( final Map<Thread, StackTraceElement[]> stackTraces, final Thread.State stateIn) { super(); Thread thread; // collect single dumps for (final Map.Entry<Thread, StackTraceElement[]> entry: stackTraces.entrySet()) { thread = entry.getKey(); final StackTraceElement[] stackTraceElements = entry.getValue(); StackTraceElement ste; String tracename = ""; final State threadState = thread.getState(); final ThreadInfo info = Memory.threadBean.getThreadInfo(thread.getId()); if (threadState != null && info != null && (stateIn == null || stateIn.equals(threadState)) && stackTraceElements.length > 0) { final StringBuilder sb = new StringBuilder(3000); final String threadtitle = tracename + "THREAD: " + thread.getName() + " " + (thread.isDaemon()?"daemon":"") + " id=" + thread.getId() + " " + threadState.toString() + (info.getLockOwnerId() >= 0 ? " lock owner =" + info.getLockOwnerId() : ""); boolean cutcore = true; for (int i = 0; i < stackTraceElements.length; i++) { ste = stackTraceElements[i]; String className = ste.getClassName(); String classString = ste.toString(); if (cutcore && (className.startsWith("java.") || className.startsWith("sun."))) { sb.setLength(0); bufferappend(sb, tracename + "at " + classString); } else { cutcore = false; bufferappend(sb, tracename + "at " + classString); } } final StackTrace stackTrace = new StackTrace(sb.toString()); SortedSet<String> threads = get(stackTrace); if (threads == null) { threads = new TreeSet<String>(); put(stackTrace, threads); } threads.add(threadtitle); } } } private void appendStackTraces( final StringBuilder buffer, final Thread.State stateIn) { bufferappend(buffer, "THREADS WITH STATES: " + stateIn.toString()); bufferappend(buffer, ""); // write dumps for (final Map.Entry<StackTrace, SortedSet<String>> entry: entrySet()) { final SortedSet<String> threads = entry.getValue(); for (final String t: threads) bufferappend(buffer, t); bufferappend(buffer, entry.getKey().text); bufferappend(buffer, ""); } bufferappend(buffer, ""); } } }