/* * This file is part of TDA - Thread Dump Analysis Tool. * * TDA is free software; you can redistribute it and/or modify * it under the terms of the Lesser GNU General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * TDA 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 * Lesser GNU General Public License for more details. * * You should have received a copy of the Lesser GNU General Public License * along with TDA; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id: AbstractDumpParser.java,v 1.21 2010-01-03 14:23:09 irockel Exp $ */ package com.pironet.tda.parser; import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.swing.ListModel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import com.pironet.tda.Category; import com.pironet.tda.CustomCategory; import com.pironet.tda.TDA; import com.pironet.tda.TableCategory; import com.pironet.tda.ThreadDumpInfo; import com.pironet.tda.ThreadInfo; import com.pironet.tda.filter.ThreadFilter; import com.pironet.tda.utils.DateMatcher; import com.pironet.tda.utils.IconFactory; import com.pironet.tda.utils.PrefManager; import fr.loicmathieu.bobbin.bo.AgreggateKey; import fr.loicmathieu.bobbin.bo.AgreggateLineInfos; import fr.loicmathieu.bobbin.bo.Line; /** * abstract dump parser class, contains all generic dump parser * stuff, which doesn't have any jdk specific parsing code. * * All Dump Parser should extend from this class as it already provides * a basic parsing interface. * * @author irockel * @author lmathieu */ public abstract class AbstractDumpParser implements DumpParser { private BufferedReader bis = null; private int markSize = 16384; private int maxCheckLines = 10; private boolean millisTimeStamp = false; private DateMatcher dm = null; private List<Line> lines = new ArrayList<>(); protected AbstractDumpParser(BufferedReader bis, DateMatcher dm) { maxCheckLines = PrefManager.get().getMaxRows(); markSize = PrefManager.get().getStreamResetBuffer(); millisTimeStamp = PrefManager.get().getMillisTimeStamp(); setBis(bis); setDm(dm); } /** * strip the dump string from a given path * * @param path the treepath to check * @return dump string, if proper tree path, null otherwise. */ protected String getDumpStringFromTreePath(TreePath path) { String[] elems = path.toString().split(","); if (elems.length > 1) { return (elems[elems.length - 1].substring(0, elems[elems.length - 1].lastIndexOf(']')).trim()); } return null; } /** * find long running threads. * * @param root the root node to use for the result. * @param dumpStore the dump store to use * @param paths paths to the dumps to check * @param minOccurence the min occurrence of a long running thread * @param regex regex to be applied to the thread titles. */ public void findLongRunningThreads(DefaultMutableTreeNode root, Map dumpStore, TreePath[] paths, int minOccurence, String regex) { diffDumps("Long running thread detection", root, dumpStore, paths, minOccurence, regex); } /** * merge the given dumps. * * @param root the root node to use for the result. * @param dumpStore the dump store tu use * @param dumps paths to the dumps to check * @param minOccurence the min occurrence of a long running thread * @param regex regex to be applied to the thread titles. */ public void mergeDumps(DefaultMutableTreeNode root, Map dumpStore, TreePath[] dumps, int minOccurence, String regex) { diffDumps("Merge", root, dumpStore, dumps, minOccurence, regex); } protected void diffDumps(String prefix, DefaultMutableTreeNode root, Map dumpStore, TreePath[] dumps, int minOccurence, String regex) { Vector keys = new Vector(dumps.length); for (TreePath dump : dumps) { String dumpName = getDumpStringFromTreePath(dump); if (dumpName.indexOf(" at") > 0) { dumpName = dumpName.substring(0, dumpName.indexOf(" at")); } else if (dumpName.indexOf(" around") > 0) { dumpName = dumpName.substring(0, dumpName.indexOf(" around")); } keys.add(dumpName); } String info = prefix + " between " + keys.get(0) + " and " + keys.get(keys.size() - 1); DefaultMutableTreeNode catMerge = new DefaultMutableTreeNode(new TableCategory(info, IconFactory.DIFF_DUMPS)); root.add(catMerge); int threadCount = 0; if (dumpStore.get(keys.get(0)) != null) { Iterator dumpIter = ((Map) dumpStore.get(keys.get(0))).keySet().iterator(); while (dumpIter.hasNext()) { String threadKey = ((String) dumpIter.next()).trim(); int occurence = 0; if (regex == null || regex.equals("") || threadKey.matches(regex)) { for (int i = 1; i < dumps.length; i++) { Map threads = (Map) dumpStore.get(keys.get(i)); if (threads.containsKey(threadKey)) { occurence++; } } if (occurence >= (minOccurence - 1)) { threadCount++; StringBuffer content = new StringBuffer("<body bgcolor=\"ffffff\"><b><font size=").append(TDA.getFontSizeModifier(-1)) .append(">").append((String) keys.get(0)).append("</b></font><hr><pre><font size=") .append(TDA.getFontSizeModifier(-1)).append(">") .append(fixMonitorLinks((String) ((Map) dumpStore.get(keys.get(0))).get(threadKey), (String) keys.get(0))); int maxLines = 0; for (int i = 1; i < dumps.length; i++) { if (((Map) dumpStore.get(keys.get(i))).containsKey(threadKey)) { content.append("\n\n</pre><b><font size="); content.append(TDA.getFontSizeModifier(-1)); content.append(">"); content.append(keys.get(i)); content.append("</font></b><hr><pre><font size="); content.append(TDA.getFontSizeModifier(-1)); content.append(">"); content.append(fixMonitorLinks((String) ((Map) dumpStore.get(keys.get(i))).get(threadKey), (String) keys.get(i))); int countLines = countLines(((String) ((Map) dumpStore.get(keys.get(i))).get(threadKey))); maxLines = maxLines > countLines ? maxLines : countLines; } } addToCategory(catMerge, threadKey, null, content.toString(), maxLines, true); } } } } ((Category) catMerge.getUserObject()).setInfo(getStatInfo(keys, prefix, minOccurence, threadCount)); } /** * count lines of input string. * * @param input * @return line count */ private int countLines(String input) { int pos = 0; int count = 0; while (input.indexOf('\n', pos) > 0) { count++; pos = input.indexOf('\n', pos) + 1; } return (count); } /** * generate statistical information concerning the merge on long running thread detection. * * @param keys the dump node keys * @param prefix the prefix of the run (either "Merge" or "Long running threads detection" * @param minOccurence the minimum occurence of threads * @param threadCount the overall thread count of this run. * @return */ private String getStatInfo(Vector keys, String prefix, int minOccurence, int threadCount) { StringBuffer statData = new StringBuffer("<body bgcolor=\"#ffffff\"><font face=System><b><font face=System> "); statData.append("<b>" + prefix + "</b><hr><p><i>"); for (int i = 0; i < keys.size(); i++) { statData.append(keys.get(i)); if (i < keys.size() - 1) { statData.append(", "); } } statData.append("</i></p><br>" + "<table border=0><tr bgcolor=\"#dddddd\"><td><font face=System " + ">Overall Thread Count</td><td width=\"150\"></td><td><b><font face=System>"); statData.append(threadCount); statData.append("</b></td></tr>"); statData.append("<tr bgcolor=\"#eeeeee\"><td><font face=System " + ">Minimum Occurence of threads</td><td width=\"150\"></td><td><b><font face=System>"); statData.append(minOccurence); statData.append("</b></td></tr>"); if (threadCount == 0) { statData.append("<tr bgcolor=\"#ffffff\"<td></td></tr>"); statData.append("<tr bgcolor=\"#cccccc\"><td colspan=2><font face=System " + "><p>No threads were found which occured at least " + minOccurence + " times.<br>" + "You should check your dumps for long running threads " + "or adjust the minimum occurence.</p>"); } statData.append("</table>"); return statData.toString(); } /** * fix the monitor links for proper navigation to the monitor in the right dump. * * @param fixString the string to fix * @param dumpName the dump name to reference * @return the fixed string. */ private String fixMonitorLinks(String fixString, String dumpName) { if (fixString.indexOf("monitor://") > 0) { fixString = fixString.replaceAll("monitor://", "monitor:/" + dumpName + "/"); } return (fixString); } /** * create a tree node with the provided information * * @param top the parent node the new node should be added to. * @param title the title of the new node * @param info the info part of the new node * @param content the content part of the new node * @see ThreadInfo */ protected void createNode(DefaultMutableTreeNode top, String title, String info, String content, int lineCount) { DefaultMutableTreeNode threadInfo = null; threadInfo = new DefaultMutableTreeNode(new ThreadInfo(title, info, content, lineCount, getThreadTokens(title))); top.add(threadInfo); } /** * create a category entry for a category (categories are "Monitors", "Threads waiting", e.g.). A ThreadInfo * instance will be created with the passed information. * * @param category the category the node should be added to. * @param title the title of the new node * @param info the info part of the new node * @param content the content part of the new node * @param lineCount the line count of the thread stack, 0 if not applicable for this element. * @param parseTokens true if tokens needs to be parsed (only applicabel for thread info) * @see ThreadInfo */ protected void addToCategory(DefaultMutableTreeNode category, String title, StringBuffer info, String content, int lineCount, boolean parseTokens) { DefaultMutableTreeNode threadInfo = null; threadInfo = new DefaultMutableTreeNode(new ThreadInfo(title, info != null ? info.toString() : null, content, lineCount, parseTokens ? getThreadTokens(title) : null)); ((Category) category.getUserObject()).addToCatNodes(threadInfo); } /** * addon LMA * create a category entry for a category (categories are "Monitors", "Threads waiting", e.g.). A ThreadInfo * instance will be created with the passed information. * * @param category the category the node should be added to. * @param row the row elements, the first element will be used as nam * @param info the info part of the new node * @param content the content part of the new node * @param lineCount the line count of the thread stack, 0 if not applicable for this element. * @see ThreadInfo */ protected void addToCategory(DefaultMutableTreeNode category, String[] row, StringBuffer info, String content, int lineCount) { DefaultMutableTreeNode threadInfo = null; threadInfo = new DefaultMutableTreeNode(new ThreadInfo(row[0], info != null ? info.toString() : null, content, lineCount, row)); ((Category) category.getUserObject()).addToCatNodes(threadInfo); } /** * get the stream to parse * * @return stream or null if none is set up */ protected BufferedReader getBis() { return bis; } /** * parse the thread tokens for table display. * * @param title */ protected abstract String[] getThreadTokens(String title); /** * set the stream to parse * * @param bis the stream */ protected void setBis(BufferedReader bis) { this.bis = bis; } /** * this counter counts backwards for adding class histograms to the thread dumpss beginning with the last dump. */ private int dumpHistogramCounter = -1; /** * set the dump histogram counter to the specified value to force to start (bottom to top) from the specified thread * dump. */ public void setDumpHistogramCounter(int value) { dumpHistogramCounter = value; } /** * retrieve the next node for adding histogram information into the tree. * * @param root the root to use for search. * @return node to use for append. */ protected DefaultMutableTreeNode getNextDumpForHistogram(DefaultMutableTreeNode root) { if (dumpHistogramCounter == -1) { // -1 as index starts with 0. dumpHistogramCounter = root.getChildCount() - 1; } DefaultMutableTreeNode result = null; if (dumpHistogramCounter > 0) { result = (DefaultMutableTreeNode) root.getChildAt(dumpHistogramCounter); dumpHistogramCounter--; } return result; } /** * close this dump parser, also closes the passed dump stream */ public void close() throws IOException { if (getBis() != null) { getBis().close(); } } /** * get the maximum size for the mark buffer while reading the log file stream. * * @return size, default is 16KB. */ protected int getMarkSize() { return markSize; } /** * set the maximum mark size. * * @param markSize the size to use, default is 16KB. */ protected void setMarkSize(int markSize) { this.markSize = markSize; } /** * specifies the maximum amounts of lines to check if the dump is followed by a class histogram or a deadlock. * * @return the amount of lines to check, defaults to 10. */ protected int getMaxCheckLines() { return maxCheckLines; } public void setMaxCheckLines(int maxCheckLines) { this.maxCheckLines = maxCheckLines; } /** * @return true, if the time stamp is in milliseconds. */ public boolean isMillisTimeStamp() { return millisTimeStamp; } public void setMillisTimeStamp(boolean millisTimeStamp) { this.millisTimeStamp = millisTimeStamp; } public DateMatcher getDm() { return dm; } public void setDm(DateMatcher dm) { this.dm = dm; } /** * check threads in given thread dump and add appropriate custom categories (if any defined). * * @param tdi the thread dump info object. */ public void addCustomCategories(DefaultMutableTreeNode threadDump) { ThreadDumpInfo tdi = (ThreadDumpInfo) threadDump.getUserObject(); Category threads = tdi.getThreads(); ListModel cats = PrefManager.get().getCategories(); for (int i = 0; i < cats.getSize(); i++) { Category cat = new TableCategory(((CustomCategory) cats.getElementAt(i)).getName(), IconFactory.CUSTOM_CATEGORY); for (int j = 0; j < threads.getNodeCount(); j++) { Iterator filterIter = ((CustomCategory) cats.getElementAt(i)).iterOfFilters(); boolean matches = true; ThreadInfo ti = (ThreadInfo) threads.getNodeAt(j).getUserObject(); while (matches && filterIter.hasNext()) { ThreadFilter filter = (ThreadFilter) filterIter.next(); matches = filter.matches(ti, true); } if (matches) { cat.addToCatNodes(new DefaultMutableTreeNode(ti)); } } if (cat.getNodeCount() > 0) { cat.setName(cat.getName() + " (" + cat.getNodeCount() + " Threads overall)"); threadDump.add(new DefaultMutableTreeNode(cat)); } } } /** * addon LMA * * @inheritDoc */ @Override public List<Line> getLines() { return lines; } /** * addon LMA */ public Collection<AgreggateLineInfos> computeLinesForPackage() { Map<AgreggateKey, AgreggateLineInfos> map = new HashMap<>(); // parsing package data for (Line line : getLines()) { AgreggateKey key = new AgreggateKey(); key.setAggregate(line.getPackageName()); key.setThreadState(line.getThreadState()); AgreggateLineInfos info = map.get(key); if (info == null) { info = new AgreggateLineInfos(); info.setKey(key); } info.getResultingLines().add(line); map.put(key, info); } List<AgreggateLineInfos> list = new ArrayList<>(map.values()); Collections.sort(list); return list; } /** * addon LMA */ public Collection<AgreggateLineInfos> computeLinesForClass() { Map<AgreggateKey, AgreggateLineInfos> map = new HashMap<>(); // parsing package data for (Line line : getLines()) { AgreggateKey key = new AgreggateKey(); key.setAggregate(line.getPackageName() + "." + line.getClassName()); key.setThreadState(line.getThreadState()); AgreggateLineInfos info = map.get(key); if (info == null) { info = new AgreggateLineInfos(); info.setKey(key); } info.getResultingLines().add(line); map.put(key, info); } List<AgreggateLineInfos> list = new ArrayList<>(map.values()); Collections.sort(list); return list; } /** * addon LMA */ public Collection<AgreggateLineInfos> computeLinesForMethod() { Map<AgreggateKey, AgreggateLineInfos> map = new HashMap<>(); // parsing package data for (Line line : getLines()) { AgreggateKey key = new AgreggateKey(); key.setAggregate(line.getPackageName() + "." + line.getClassName() + "." + line.getMethodName() + "()"); key.setThreadState(line.getThreadState()); AgreggateLineInfos info = map.get(key); if (info == null) { info = new AgreggateLineInfos(); info.setKey(key); } info.getResultingLines().add(line); map.put(key, info); } List<AgreggateLineInfos> list = new ArrayList<>(map.values()); Collections.sort(list); return list; } /** * addon LMA */ public Collection<AgreggateLineInfos> computeLinesForMethodWithLine() { Map<AgreggateKey, AgreggateLineInfos> map = new HashMap<>(); // parsing package data for (Line line : getLines()) { AgreggateKey key = new AgreggateKey(); key.setAggregate(line.getPackageName() + "." + line.getClassName() + "." + line.getMethodName() + "():" + line.getLineNumber()); key.setThreadState(line.getThreadState()); AgreggateLineInfos info = map.get(key); if (info == null) { info = new AgreggateLineInfos(); info.setKey(key); } info.getResultingLines().add(line); map.put(key, info); } List<AgreggateLineInfos> list = new ArrayList<>(map.values()); Collections.sort(list); return list; } }