com.pironet.tda.SunJDKParser.java Source code

Java tutorial

Introduction

Here is the source code for com.pironet.tda.SunJDKParser.java

Source

/*
 * SunJDKParser.java
 *
 * 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: SunJDKParser.java,v 1.47 2010-01-03 14:23:09 irockel Exp $
 */
package com.pironet.tda;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;

import com.google.common.base.Strings;
import com.pironet.tda.utils.DateMatcher;
import com.pironet.tda.utils.HistogramTableModel;
import com.pironet.tda.utils.IconFactory;

import org.apache.commons.io.IOUtils;

/**
 * Parses SunJDK Thread Dumps. Also parses SAP and HP Dumps.
 * Needs to be closed after use (so inner stream is closed).
 *
 * @author irockel
 */
public class SunJDKParser extends AbstractDumpParser {

    private static final Pattern LT_PATTERN = Pattern.compile("<");
    private static final Pattern AT_PATTERN = Pattern.compile("@");
    private static final Pattern PATTERN_NONAME = Pattern.compile("<no name>");
    private static final Pattern PATTERN_SPACE = Pattern.compile("(\\s)+");

    private MutableTreeNode nextDump = null;
    private Map<String, Map<String, String>> threadStore = null;
    private int counter = 1;
    private int lineCounter = 0;
    private boolean foundClassHistograms = false;
    private boolean withCurrentTimeStamp = false;

    /**
     * Creates a new instance of SunJDKParser
     */
    public SunJDKParser(BufferedReader bis, Map<String, Map<String, String>> threadStore, int lineCounter,
            boolean withCurrentTimeStamp, int startCounter, DateMatcher dm) {
        super(bis, dm);
        this.threadStore = threadStore;
        this.withCurrentTimeStamp = withCurrentTimeStamp;
        this.lineCounter = lineCounter;
        this.counter = startCounter;
    }

    /**
     * returns true if at least one more dump available, already loads it
     * (this will be returned on next call of parseNext)
     */
    public boolean hasMoreDumps() {
        nextDump = parseNext();
        return (nextDump != null);
    }

    /**
     * @return true, if a class histogram was found and added during parsing.
     */
    public boolean isFoundClassHistograms() {
        return (foundClassHistograms);
    }

    /**
     * parse the next thread dump from the stream passed with the constructor.
     *
     * @return null if no more thread dumps were found.
     */
    public MutableTreeNode parseNext() {
        if (nextDump != null) {
            MutableTreeNode tmpDump = nextDump;
            nextDump = null;
            return (tmpDump);
        }
        boolean retry = false;
        String line = null;

        do {

            try {
                Map<String, String> threads = new HashMap<>();
                ThreadDumpInfo overallTDI = new ThreadDumpInfo("Dump No. " + counter++, 0);
                if (withCurrentTimeStamp) {
                    overallTDI.setStartTime((new Date(System.currentTimeMillis())).toString());
                }
                DefaultMutableTreeNode threadDump = new DefaultMutableTreeNode(overallTDI);

                DefaultMutableTreeNode catThreads = new DefaultMutableTreeNode(
                        new TableCategory("Threads", IconFactory.THREADS));
                threadDump.add(catThreads);

                DefaultMutableTreeNode catWaiting = new DefaultMutableTreeNode(
                        new TableCategory("Threads waiting for Monitors", IconFactory.THREADS_WAITING));

                DefaultMutableTreeNode catSleeping = new DefaultMutableTreeNode(
                        new TableCategory("Threads sleeping on Monitors", IconFactory.THREADS_SLEEPING));

                DefaultMutableTreeNode catLocking = new DefaultMutableTreeNode(
                        new TableCategory("Threads locking Monitors", IconFactory.THREADS_LOCKING));

                // create category for monitors with disabled filtering.
                // NOTE:  These strings are "magic" in that the methods
                // TDA#displayCategory and TreeCategory#getCatComponent both
                // checks these literal strings and the behavior differs.
                DefaultMutableTreeNode catMonitors = new DefaultMutableTreeNode(
                        new TreeCategory("Monitors", IconFactory.MONITORS, false));
                DefaultMutableTreeNode catMonitorsLocks = new DefaultMutableTreeNode(
                        new TreeCategory("Monitors without locking thread", IconFactory.MONITORS_NO_LOCKS, false));
                DefaultMutableTreeNode catBlockingMonitors = new DefaultMutableTreeNode(
                        new TreeCategory("Threads blocked by Monitors", IconFactory.THREADS_LOCKING, false));

                String title = null;
                String dumpKey = null;
                StringBuffer content = null;
                boolean inLocking = false;
                boolean inSleeping = false;
                boolean inWaiting = false;
                int threadCount = 0;
                int waiting = 0;
                int locking = 0;
                int sleeping = 0;
                boolean locked = true;
                boolean finished = false;
                final MonitorMap mmap = new MonitorMap();
                final Stack<String> monitorStack = new Stack<>();
                long startTime = 0;
                int singleLineCounter = 0;
                boolean concurrentSyncsFlag = false;
                Matcher matched = getDm().getLastMatch();

                while (getBis().ready() && !finished) {
                    line = getNextLine();
                    lineCounter++;
                    singleLineCounter++;
                    if (locked) {
                        if (line.contains("Full thread dump")) {
                            locked = false;
                            if (!withCurrentTimeStamp) {
                                overallTDI.setLogLine(lineCounter);

                                if (startTime != 0) {
                                    startTime = 0;
                                } else if (matched != null && matched.matches()) {

                                    String parsedStartTime = matched.group(1);
                                    if (!getDm().isDefaultMatches() && isMillisTimeStamp()) {
                                        try {
                                            // the factor is a hack for a bug in oc4j timestamp printing (pattern timeStamp=2342342340)
                                            if (parsedStartTime.length() < 13) {
                                                startTime = Long.parseLong(parsedStartTime)
                                                        * (long) Math.pow(10, 13 - parsedStartTime.length());
                                            } else {
                                                startTime = Long.parseLong(parsedStartTime);
                                            }
                                        } catch (NumberFormatException nfe) {
                                            startTime = 0;
                                        }
                                        if (startTime > 0) {
                                            overallTDI.setStartTime((new Date(startTime)).toString());
                                        }
                                    } else {
                                        overallTDI.setStartTime(parsedStartTime);
                                    }
                                    matched = null;
                                    getDm().resetLastMatch();
                                }
                            }
                            dumpKey = overallTDI.getName();
                        } else if (!getDm().isPatternError() && (getDm().getRegexPattern() != null)) {
                            Matcher m = getDm().checkForDateMatch(line);
                            if (m != null) {
                                matched = m;
                            }
                        }
                    } else {
                        if (line.startsWith("\"")) {
                            // We are starting a group of lines for a different thread
                            // First, flush state for the previous thread (if any)
                            concurrentSyncsFlag = false;
                            String stringContent = content != null ? content.toString() : null;
                            if (title != null) {
                                threads.put(title, content.toString());
                                content.append("</pre></pre>");
                                addToCategory(catThreads, title, null, stringContent, singleLineCounter, true);
                                threadCount++;
                            }
                            if (inWaiting) {
                                addToCategory(catWaiting, title, null, stringContent, singleLineCounter, true);
                                inWaiting = false;
                                waiting++;
                            }
                            if (inSleeping) {
                                addToCategory(catSleeping, title, null, stringContent, singleLineCounter, true);
                                inSleeping = false;
                                sleeping++;
                            }
                            if (inLocking) {
                                addToCategory(catLocking, title, null, stringContent, singleLineCounter, true);
                                inLocking = false;
                                locking++;
                            }
                            singleLineCounter = 0;
                            while (!monitorStack.empty()) {
                                mmap.parseAndAddThread(monitorStack.pop(), title, content.toString());
                            }

                            // Second, initialize state for this new thread
                            title = line;
                            content = new StringBuffer("<body bgcolor=\"ffffff\"><pre><font size="
                                    + TDA.getFontSizeModifier(-1) + '>');
                            content.append(line);
                            content.append('\n');
                        } else if (line.contains("at ")) {
                            content.append(line);
                            content.append('\n');
                        } else if (line.contains("java.lang.Thread.State")) {
                            content.append(line);
                            content.append('\n');
                            if (title.indexOf("t@") > 0) {
                                // in this case the title line is missing state informations
                                String state = line.substring(line.indexOf(':') + 1).trim();
                                if (state.indexOf(' ') > 0) {
                                    title += " state=" + state.substring(0, state.indexOf(' '));
                                } else {
                                    title += " state=" + state;
                                }
                            }
                        } else if (line.contains("Locked ownable synchronizers:")) {
                            concurrentSyncsFlag = true;
                            content.append(line);
                            content.append('\n');
                        } else if (line.contains("- waiting on")) {
                            content.append(linkifyMonitor(line));
                            monitorStack.push(line);
                            inSleeping = true;
                            content.append('\n');
                        } else if (line.contains("- parking to wait")) {
                            content.append(linkifyMonitor(line));
                            monitorStack.push(line);
                            inSleeping = true;
                            content.append('\n');
                        } else if (line.contains("- waiting to")) {
                            content.append(linkifyMonitor(line));
                            monitorStack.push(line);
                            inWaiting = true;
                            content.append('\n');
                        } else if (line.contains("- locked")) {
                            content.append(linkifyMonitor(line));
                            inLocking = true;
                            monitorStack.push(line);
                            content.append('\n');
                        } else if (line.contains("- ")) {
                            if (concurrentSyncsFlag) {
                                content.append(linkifyMonitor(line));
                                monitorStack.push(line);
                            } else {
                                content.append(line);
                            }
                            content.append('\n');
                        }

                        // last thread reached?
                        if ((line.contains("\"Suspend Checker Thread\""))
                                || (line.contains("\"VM Periodic Task Thread\""))
                                || (line.contains("<EndOfDump>"))) {
                            finished = true;
                            getBis().mark(getMarkSize());
                            if ((checkForDeadlocks(threadDump)) == 0) {
                                // no deadlocks found, set back original position.
                                getBis().reset();
                            }

                            if (!checkThreadDumpStatData(overallTDI)) {
                                // no statistical data found, set back original position.
                                getBis().reset();
                            }

                            getBis().mark(getMarkSize());
                            if (!(foundClassHistograms = checkForClassHistogram(threadDump))) {
                                getBis().reset();
                            }
                        }
                    }
                }
                // last thread
                String stringContent = content != null ? content.toString() : null;
                if (title != null) {
                    threads.put(title, content.toString());
                    content.append("</pre></pre>");
                    addToCategory(catThreads, title, null, stringContent, singleLineCounter, true);
                    threadCount++;
                }
                if (inWaiting) {
                    addToCategory(catWaiting, title, null, stringContent, singleLineCounter, true);
                    waiting++;
                }
                if (inSleeping) {
                    addToCategory(catSleeping, title, null, stringContent, singleLineCounter, true);
                    sleeping++;
                }
                if (inLocking) {
                    addToCategory(catLocking, title, null, stringContent, singleLineCounter, true);
                    locking++;
                }
                while (!monitorStack.empty()) {
                    mmap.parseAndAddThread(monitorStack.pop(), title, content.toString());
                }

                int monitorCount = mmap.size();

                int monitorsWithoutLocksCount = 0;
                int contendedMonitors = 0;
                int blockedThreads = 0;
                // dump monitors 
                if (mmap.size() > 0) {
                    int[] result = dumpMonitors(catMonitors, catMonitorsLocks, mmap);
                    monitorsWithoutLocksCount = result[0];
                    overallTDI.setOverallThreadsWaitingWithoutLocksCount(result[1]);

                    result = dumpBlockingMonitors(catBlockingMonitors, mmap);
                    contendedMonitors = result[0];
                    blockedThreads = result[1];
                }

                // display nodes with stuff to display
                if (waiting > 0) {
                    overallTDI.setWaitingThreads((Category) catWaiting.getUserObject());
                    threadDump.add(catWaiting);
                }

                if (sleeping > 0) {
                    overallTDI.setSleepingThreads((Category) catSleeping.getUserObject());
                    threadDump.add(catSleeping);
                }

                if (locking > 0) {
                    overallTDI.setLockingThreads((Category) catLocking.getUserObject());
                    threadDump.add(catLocking);
                }

                if (monitorCount > 0) {
                    overallTDI.setMonitors((Category) catMonitors.getUserObject());
                    threadDump.add(catMonitors);
                }

                if (contendedMonitors > 0) {
                    overallTDI.setBlockingMonitors((Category) catBlockingMonitors.getUserObject());
                    threadDump.add(catBlockingMonitors);
                }

                if (monitorsWithoutLocksCount > 0) {
                    overallTDI.setMonitorsWithoutLocks((Category) catMonitorsLocks.getUserObject());
                    threadDump.add(catMonitorsLocks);
                }
                overallTDI.setThreads((Category) catThreads.getUserObject());

                ((Category) catThreads.getUserObject())
                        .setName(catThreads.getUserObject() + " (" + threadCount + " Threads overall)");
                ((Category) catWaiting.getUserObject())
                        .setName(catWaiting.getUserObject() + " (" + waiting + " Threads waiting)");
                ((Category) catSleeping.getUserObject())
                        .setName(catSleeping.getUserObject() + " (" + sleeping + " Threads sleeping)");
                ((Category) catLocking.getUserObject())
                        .setName(catLocking.getUserObject() + " (" + locking + " Threads locking)");
                ((Category) catMonitors.getUserObject())
                        .setName(catMonitors.getUserObject() + " (" + monitorCount + " Monitors)");
                ((Category) catBlockingMonitors.getUserObject()).setName(catBlockingMonitors.getUserObject() + " ("
                        + blockedThreads + " Threads blocked by " + contendedMonitors + " Monitors)");
                ((Category) catMonitorsLocks.getUserObject()).setName(
                        catMonitorsLocks.getUserObject() + " (" + monitorsWithoutLocksCount + " Monitors)");
                // add thread dump to passed dump store.
                if ((threadCount > 0) && (dumpKey != null)) {
                    threadStore.put(dumpKey.trim(), threads);
                }

                // check custom categories
                addCustomCategories(threadDump);

                return (threadCount > 0 ? threadDump : null);
            } catch (StringIndexOutOfBoundsException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(null,
                        "Error during parsing of a found thread dump, skipping to next one!\n"
                                + "Check for possible broken dumps, sometimes, stream flushing mixes the logged data.\n"
                                + "Error Message is \"" + e.getLocalizedMessage() + "\". \n"
                                + (line != null ? "Last line read was \"" + line + "\". \n" : ""),
                        "Error during Parsing Thread Dump", JOptionPane.ERROR_MESSAGE);
                retry = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        } while (retry);

        return (null);
    }

    /**
     * add a monitor link for monitor navigation
     *
     * @param line containing monitor
     */
    private String linkifyMonitor(String line) {
        if (line != null && line.indexOf('<') >= 0) {
            String begin = line.substring(0, line.indexOf('<'));
            String monitor = line.substring(line.indexOf('<'), line.indexOf('>') + 1);
            String end = line.substring(line.indexOf('>') + 1);
            monitor = LT_PATTERN.matcher(monitor).replaceAll("<a href=\"monitor://" + monitor + "\">&lt;");
            monitor = monitor.substring(0, monitor.length() - 1) + "&gt;</a>";
            return (begin + monitor + end);
        } else if (line != null && line.indexOf('@') >= 0) {
            String begin = line.substring(0, line.indexOf('@') + 1);
            String monitor = line.substring(line.indexOf('@'));
            monitor = AT_PATTERN.matcher(monitor)
                    .replaceAll("@<a href=\"monitor://<" + monitor.substring(1) + ">\">");
            monitor = monitor.substring(0, monitor.length() - 1) + "</a>";
            return (begin + monitor);
        } else {
            return (line);
        }
    }

    /**
     * add a monitor link for monitor navigation
     *
     * @param line containing monitor
     */
    private String linkifyDeadlockInfo(String line) {
        if (line != null && line.contains("Ox")) {
            String begin = line.substring(0, line.indexOf("0x"));
            int objectBegin = line.lastIndexOf("0x");
            int monitorBegin = line.indexOf("0x");
            String monitorHex = line.substring(monitorBegin, monitorBegin + 10);

            String monitor = line.substring(objectBegin, objectBegin + 10);
            String end = line.substring(line.indexOf("0x") + 10);

            monitor = "<a href=\"monitor://<" + monitor + ">\">" + monitorHex + "</a>";
            return (begin + monitor + end);
        } else {
            return (line);
        }
    }

    /**
     * checks for the next class histogram and adds it to the tree node passed
     *
     * @param threadDump which tree node to add the histogram.
     */
    private boolean checkForClassHistogram(DefaultMutableTreeNode threadDump) throws IOException {
        HistogramTableModel classHistogram = parseNextClassHistogram(getBis());

        if (classHistogram.getRowCount() > 0) {
            addHistogramToDump(threadDump, classHistogram);
        }

        return (classHistogram.getRowCount() > 0);
    }

    private void addHistogramToDump(DefaultMutableTreeNode threadDump, HistogramTableModel classHistogram) {
        final HistogramInfo hi = new HistogramInfo("Class Histogram of Dump", classHistogram);
        final DefaultMutableTreeNode catHistogram = new DefaultMutableTreeNode(hi);
        threadDump.add(catHistogram);
    }

    /**
     * parses the next class histogram found in the stream, uses the max check
     * lines option to check how many lines to parse in advance.
     * <p>
     * This could be called from parseLoggcFile, which is outside our normal
     * calling stream. Thus, we have to pass in the BufferedReader. However, to
     * handle a WrappedSunJDKParser, we have to use getNextLine() if possible.
     *
     * @param bis the stream to read.
     */
    private HistogramTableModel parseNextClassHistogram(BufferedReader bis) throws IOException {
        boolean finished = false;
        boolean found = false;
        final HistogramTableModel classHistogram = new HistogramTableModel();
        int maxLinesCounter = 0;

        boolean isNormalBis = bis == getBis();

        while (bis.ready() && !finished) {
            String line = (isNormalBis) ? getNextLine().trim() : bis.readLine().trim();
            if (!found && !Strings.isNullOrEmpty(line)) {
                if (line.startsWith("num   #instances    #bytes  class name")) {
                    found = true;
                } else if (maxLinesCounter >= getMaxCheckLines()) {
                    finished = true;
                } else {
                    maxLinesCounter++;
                }
            } else if (found) {
                if (line.startsWith("Total ")) {
                    // split string.
                    String newLine = PATTERN_SPACE.matcher(line).replaceAll(";");
                    String[] elems = newLine.split(";");
                    classHistogram.setBytes(Long.parseLong(elems[2]));
                    classHistogram.setInstances(Long.parseLong(elems[1]));
                    finished = true;
                } else if (!line.startsWith("-------")) {
                    // removed blank, breaks splitting using blank...
                    String newLine = PATTERN_NONAME.matcher(line).replaceAll("<no-name>");

                    // split string.
                    newLine = PATTERN_SPACE.matcher(newLine).replaceAll(";");
                    String[] elems = newLine.split(";");

                    if (elems.length == 4) {
                        classHistogram.addEntry(elems[3].trim(), Integer.parseInt(elems[2].trim()),
                                Integer.parseInt(elems[1].trim()));
                    } else {
                        classHistogram.setIncomplete(true);
                        finished = true;
                    }

                }
            }
        }

        return (classHistogram);
    }

    /**
     * Heap
     * PSYoungGen      total 6656K, used 3855K [0xb0850000, 0xb0f50000, 0xb4130000)
     * eden space 6144K, 54% used [0xb0850000,0xb0b97740,0xb0e50000)
     * from space 512K, 97% used [0xb0ed0000,0xb0f4c5c0,0xb0f50000)
     * to   space 512K, 0% used [0xb0e50000,0xb0e50000,0xb0ed0000)
     * PSOldGen        total 15552K, used 13536K [0x94130000, 0x95060000, 0xb0850000)
     * object space 15552K, 87% used [0x94130000,0x94e68168,0x95060000)
     * PSPermGen       total 16384K, used 13145K [0x90130000, 0x91130000, 0x94130000)
     * object space 16384K, 80% used [0x90130000,0x90e06610,0x91130000)
     *
     * @return
     * @throws IOException
     */
    private boolean checkThreadDumpStatData(ThreadDumpInfo tdi) throws IOException {
        boolean finished = false;
        boolean found = false;
        StringBuilder hContent = new StringBuilder();
        int heapLineCounter = 0;
        int lines = 0;

        while (getBis().ready() && !finished) {
            String line = getNextLine();
            if (!found && !Strings.isNullOrEmpty(line)) {
                if (line.trim().startsWith("Heap")) {
                    found = true;
                } else if (lines >= getMaxCheckLines()) {
                    finished = true;
                } else {
                    lines++;
                }
            } else if (found) {
                if (heapLineCounter < 7) {
                    hContent.append(line).append('\n');
                } else {
                    finished = true;
                }
                heapLineCounter++;
            }
        }
        if (hContent.length() > 0) {
            tdi.setHeapInfo(new HeapInfo(hContent.toString()));
        }

        return (found);
    }

    /**
     * check if any dead lock information is logged in the stream
     *
     * @param threadDump which tree node to add the histogram.
     */
    private int checkForDeadlocks(DefaultMutableTreeNode threadDump) throws IOException {
        boolean finished = false;
        boolean found = false;
        int deadlocks = 0;
        int lineCounter = 0;
        StringBuffer dContent = new StringBuffer();
        TreeCategory deadlockCat = new TreeCategory("Deadlocks", IconFactory.DEADLOCKS);
        DefaultMutableTreeNode catDeadlocks = new DefaultMutableTreeNode(deadlockCat);
        boolean first = true;

        while (getBis().ready() && !finished) {
            String line = getNextLine();

            if (!found && !Strings.isNullOrEmpty(line)) {
                if (line.trim().startsWith("Found one Java-level deadlock")) {
                    found = true;
                    dContent.append("<body bgcolor=\"ffffff\"><font size=").append(TDA.getFontSizeModifier(-1))
                            .append("><b>");
                    dContent.append("Found one Java-level deadlock");
                    dContent.append("</b><hr></font><pre>\n");
                } else if (lineCounter >= getMaxCheckLines()) {
                    finished = true;
                } else {
                    lineCounter++;
                }
            } else if (found) {
                if (line.startsWith("Found one Java-level deadlock")) {
                    if (dContent.length() > 0) {
                        deadlocks++;
                        addToCategory(catDeadlocks, "Deadlock No. " + (deadlocks), null, dContent.toString(), 0,
                                false);
                    }
                    dContent = new StringBuffer();
                    dContent.append("</pre><b><font size=").append(TDA.getFontSizeModifier(-1)).append('>');
                    dContent.append("Found one Java-level deadlock");
                    dContent.append("</b><hr></font><pre>\n");
                    first = true;
                } else if ((line.contains("Found"))
                        && (line.endsWith("deadlocks.") || line.endsWith("deadlock."))) {
                    finished = true;
                } else if (line.startsWith("=======")) {
                    // ignore this line
                } else if (line.contains(" monitor 0x")) {
                    dContent.append(linkifyDeadlockInfo(line));
                    dContent.append('\n');
                } else if (line.contains("Java stack information for the threads listed above")) {
                    dContent.append("</pre><br><font size=").append(TDA.getFontSizeModifier(-1)).append("><b>");
                    dContent.append("Java stack information for the threads listed above");
                    dContent.append("</b><hr></font><pre>");
                    first = true;
                } else if ((line.contains("- waiting on")) || (line.contains("- waiting to"))
                        || (line.contains("- locked")) || (line.contains("- parking to wait"))) {

                    dContent.append(linkifyMonitor(line));
                    dContent.append('\n');

                } else if (line.trim().startsWith("\"")) {
                    dContent.append("</pre>");
                    if (first) {
                        first = false;
                    } else {
                        dContent.append("<br>");
                    }
                    dContent.append("<b><font size=").append(TDA.getFontSizeModifier(-1)).append("><code>");
                    dContent.append(line);
                    dContent.append("</font></code></b><pre>");
                } else {
                    dContent.append(line);
                    dContent.append('\n');
                }
            }
        }
        if (dContent.length() > 0) {
            deadlocks++;
            addToCategory(catDeadlocks, "Deadlock No. " + (deadlocks), null, dContent.toString(), 0, false);
        }

        if (deadlocks > 0) {
            threadDump.add(catDeadlocks);
            ((ThreadDumpInfo) threadDump.getUserObject()).setDeadlocks((TreeCategory) catDeadlocks.getUserObject());
            deadlockCat.setName("Deadlocks (" + deadlocks + (deadlocks == 1 ? " deadlock)" : " deadlocks)"));
        }

        return (deadlocks);
    }

    /**
     * dump the monitor information
     */
    private int[] dumpMonitors(DefaultMutableTreeNode catMonitors, DefaultMutableTreeNode catMonitorsLocks,
            MonitorMap mmap) {
        Iterator iter = mmap.iterOfKeys();
        int monitorsWithoutLocksCount = 0;
        int overallThreadsWaiting = 0;
        while (iter.hasNext()) {
            String monitor = (String) iter.next();
            Map<String, String>[] threads = mmap.getFromMonitorMap(monitor);
            ThreadInfo mi = new ThreadInfo(monitor, null, "", 0, null);
            DefaultMutableTreeNode monitorNode = new DefaultMutableTreeNode(mi);

            // first the locks
            Iterator<String> iterLocks = threads[MonitorMap.LOCK_THREAD_POS].keySet().iterator();
            int locks = 0;
            int sleeps = 0;
            while (iterLocks.hasNext()) {
                String thread = iterLocks.next();
                String stackTrace = threads[MonitorMap.LOCK_THREAD_POS].get(thread);
                if (threads[MonitorMap.SLEEP_THREAD_POS].containsKey(thread)) {
                    createNode(monitorNode, "locks and sleeps on monitor: " + thread, null, stackTrace, 0);
                    sleeps++;
                } else if (threads[MonitorMap.WAIT_THREAD_POS].containsKey(thread)) {
                    createNode(monitorNode, "locks and waits on monitor: " + thread, null, stackTrace, 0);
                    sleeps++;
                } else {
                    createNode(monitorNode, "locked by " + thread, null, stackTrace, 0);
                }
                locks++;
            }

            Iterator iterWaits = threads[MonitorMap.WAIT_THREAD_POS].keySet().iterator();
            int waits = 0;
            while (iterWaits.hasNext()) {
                String thread = (String) iterWaits.next();
                if (!threads[MonitorMap.LOCK_THREAD_POS].containsKey(thread)) {
                    createNode(monitorNode, "waits on monitor: " + thread, null,
                            threads[MonitorMap.WAIT_THREAD_POS].get(thread), 0);
                    waits++;
                }
            }

            mi.setContent(ThreadDumpInfo.getMonitorInfo(locks, waits, sleeps));
            mi.setName(mi.getName() + ":    " + (sleeps) + " Thread(s) sleeping, " + (waits)
                    + " Thread(s) waiting, " + (locks) + " Thread(s) locking");
            if (ThreadDumpInfo.areALotOfWaiting(waits)) {
                mi.setALotOfWaiting(true);
            }
            mi.setChildCount(monitorNode.getChildCount());

            ((Category) catMonitors.getUserObject()).addToCatNodes(monitorNode);
            if (locks == 0) {
                monitorsWithoutLocksCount++;
                overallThreadsWaiting += waits;
                ((Category) catMonitorsLocks.getUserObject()).addToCatNodes(monitorNode);
            }
        }
        return new int[] { monitorsWithoutLocksCount, overallThreadsWaiting };
    }

    private int[] dumpBlockingMonitors(DefaultMutableTreeNode catLockingTree, MonitorMap mmap) {
        final Map<String, DefaultMutableTreeNode> directChildMap = new HashMap<>(); // Top level of our display model

        //******************************************************************
        // Figure out what threads are blocking and what threads are blocked
        //******************************************************************
        int blockedThreads = fillBlockingThreadMaps(mmap, directChildMap);
        int contendedLocks = directChildMap.size();

        //********************************************************************
        // Renormalize this from a flat tree (depth==1) into a structured tree
        //********************************************************************
        reNormalizeBlockingThreadTree(mmap, directChildMap);

        //********************************************************************
        // Recalculate the number of blocked threads and add remaining top-level threads to our display model
        //********************************************************************
        for (final Object o : directChildMap.entrySet()) {
            DefaultMutableTreeNode threadNode = (DefaultMutableTreeNode) ((Map.Entry) o).getValue();
            updateChildCount(threadNode, true);
            ((Category) catLockingTree.getUserObject()).addToCatNodes(threadNode);
        }

        directChildMap.clear();
        return new int[] { contendedLocks, blockedThreads };
    }

    private void reNormalizeBlockingThreadTree(final MonitorMap mmap,
            final Map<String, DefaultMutableTreeNode> directChildMap) {
        Map<String, DefaultMutableTreeNode> allBlockingThreadsMap = new HashMap<>(directChildMap); // All threads that are blocking at least one other thread

        // First, re-normalize based on monitors to get our unique tree
        // Tree will be unique as long as there are no deadlocks aka monitor loops
        for (Iterator iter = mmap.iterOfKeys(); iter.hasNext();) {
            String monitor1 = (String) iter.next();
            Map<String, String>[] threads1 = mmap.getFromMonitorMap(monitor1);

            final DefaultMutableTreeNode thread1Node = allBlockingThreadsMap.get(monitor1);
            if (thread1Node == null) {
                continue;
            }

            // Get information on the one thread holding this lock
            Iterator it = threads1[MonitorMap.LOCK_THREAD_POS].keySet().iterator();
            if (!it.hasNext()) {
                continue;
            }
            String threadLine1 = (String) it.next();

            for (Iterator iter2 = mmap.iterOfKeys(); iter2.hasNext();) {
                String monitor2 = (String) iter2.next();
                if (monitor1 == monitor2) {
                    continue;
                }

                Map<String, String>[] threads2 = mmap.getFromMonitorMap(monitor2);
                if (threads2[MonitorMap.WAIT_THREAD_POS].containsKey(threadLine1)) {
                    // Get the node of the thread that is holding this lock
                    DefaultMutableTreeNode thread2Node = (DefaultMutableTreeNode) allBlockingThreadsMap
                            .get(monitor2);
                    // Get the node of the monitor itself
                    DefaultMutableTreeNode monitor2Node = (DefaultMutableTreeNode) thread2Node.getFirstChild();

                    // If a redundant node for thread2 exists with no children, remove it
                    // To compare, we have to remove "Thread - " from the front of display strings
                    for (int i = 0; i < monitor2Node.getChildCount(); i++) {
                        DefaultMutableTreeNode child2 = (DefaultMutableTreeNode) monitor2Node.getChildAt(i);
                        if (child2.toString().substring(9).equals(threadLine1) && child2.getChildCount() == 0) {
                            monitor2Node.remove(i);
                            break;
                        }
                    }

                    // Thread1 is blocked by monitor2 held by thread2, so move thread1 under thread2
                    monitor2Node.insert(thread1Node, 0);
                    directChildMap.remove(monitor1);
                    break;
                }
            }
        }

        allBlockingThreadsMap.clear();

        // Second, re-normalize top level based on threads for cases where one thread holds multiple monitors
        boolean changed;
        do {
            changed = false;
            for (final Object o : directChildMap.entrySet()) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) ((Map.Entry) o).getValue();
                if (checkForDuplicateThreadItem(directChildMap, node)) {
                    changed = true;
                    break;
                }
            }
        } while (changed);

        // Third, renormalize lower levels of the tree based on threads for cases where one thread holds multiple monitors
        for (final Object o : directChildMap.entrySet()) {
            renormalizeThreadDepth((DefaultMutableTreeNode) ((Map.Entry) o).getValue());
        }
    }

    private void renormalizeThreadDepth(DefaultMutableTreeNode threadNode1) {
        for (Enumeration e = threadNode1.children(); e.hasMoreElements();) {
            DefaultMutableTreeNode monitorNode2 = (DefaultMutableTreeNode) e.nextElement();
            for (int ii = 0; ii < monitorNode2.getChildCount(); ii++) {
                renormalizeMonitorDepth(monitorNode2, ii);
            }
        }
    }

    private void renormalizeMonitorDepth(DefaultMutableTreeNode monitorNode, int index) {
        // First, remove all duplicates of the item at index "index"
        DefaultMutableTreeNode threadNode1 = (DefaultMutableTreeNode) monitorNode.getChildAt(index);
        ThreadInfo mi1 = (ThreadInfo) threadNode1.getUserObject();
        int i = index + 1;
        while (i < monitorNode.getChildCount()) {
            DefaultMutableTreeNode threadNode2 = (DefaultMutableTreeNode) monitorNode.getChildAt(i);
            ThreadInfo mi2 = (ThreadInfo) threadNode2.getUserObject();
            if (mi1.getName().equals(mi2.getName())) {
                if (threadNode2.getChildCount() > 0) {
                    threadNode1.add((DefaultMutableTreeNode) threadNode2.getFirstChild());
                    monitorNode.remove(i);
                    continue;
                }
            }
            i++;
        }

        // Second, recurse into item "index"
        renormalizeThreadDepth(threadNode1);
    }

    private boolean checkForDuplicateThreadItem(Map directChildMap, DefaultMutableTreeNode node1) {
        ThreadInfo mi1 = (ThreadInfo) node1.getUserObject();
        String name1 = mi1.getName();

        for (Iterator iter2 = directChildMap.entrySet().iterator(); iter2.hasNext();) {
            DefaultMutableTreeNode node2 = (DefaultMutableTreeNode) ((Map.Entry) iter2.next()).getValue();
            if (node1 == node2) {
                continue;
            }

            ThreadInfo mi2 = (ThreadInfo) node2.getUserObject();
            if (name1.equals(mi2.getName()) && node2.getChildCount() > 0) {
                node1.add((MutableTreeNode) node2.getFirstChild());
                iter2.remove();
                return true;
            }
        }

        return false;
    }

    private int fillBlockingThreadMaps(final MonitorMap mmap,
            final Map<String, DefaultMutableTreeNode> directChildMap) {
        int blockedThread = 0;
        for (Iterator iter = mmap.iterOfKeys(); iter.hasNext();) {
            String monitor = (String) iter.next();
            Map<String, String>[] threads = mmap.getFromMonitorMap(monitor);

            // Only one thread can really be holding this monitor, so find the thread
            String threadLine = getLockingThread(threads);
            ThreadInfo tmi = new ThreadInfo("Thread - " + threadLine, null, "", 0, null);
            DefaultMutableTreeNode threadNode = new DefaultMutableTreeNode(tmi);

            ThreadInfo mmi = new ThreadInfo("Monitor - " + monitor, null, "", 0, null);
            DefaultMutableTreeNode monitorNode = new DefaultMutableTreeNode(mmi);
            threadNode.add(monitorNode);

            // Look over all threads blocked on this monitor
            for (final Object o : threads[MonitorMap.WAIT_THREAD_POS].keySet()) {
                String thread = (String) o;
                // Skip the thread that has this monitor locked
                if (!threads[MonitorMap.LOCK_THREAD_POS].containsKey(thread)) {
                    blockedThread++;
                    createNode(monitorNode, "Thread - " + thread, null,
                            threads[MonitorMap.WAIT_THREAD_POS].get(thread), 0);
                }
            }

            final String blockingStackFrame = threads[MonitorMap.LOCK_THREAD_POS].get(threadLine);
            tmi.setContent(blockingStackFrame);
            mmi.setContent("This monitor (" + linkifyMonitor(monitor)
                    + ") is held in the following stack frame:\n\n" + blockingStackFrame);

            // If no-one is blocked on or waiting for this monitor, don't show it
            if (monitorNode.getChildCount() > 0) {
                directChildMap.put(monitor, threadNode);
            }
        }
        return blockedThread;
    }

    private String getLockingThread(Map[] threads) {
        int lockingThreadCount = threads[MonitorMap.LOCK_THREAD_POS].keySet().size();
        if (lockingThreadCount == 1) {
            return (String) threads[MonitorMap.LOCK_THREAD_POS].keySet().iterator().next();
        }

        for (final Object o : threads[MonitorMap.LOCK_THREAD_POS].keySet()) {
            String thread = (String) o;
            if (!threads[MonitorMap.SLEEP_THREAD_POS].containsKey(thread)) {
                return thread;
            }
        }

        return "";
    }

    private void updateChildCount(DefaultMutableTreeNode threadOrMonitorNode, boolean isThreadNode) {
        int count = 0;
        for (Enumeration e = threadOrMonitorNode.depthFirstEnumeration(); e.hasMoreElements();) {
            Object element = e.nextElement();
            ThreadInfo mi = (ThreadInfo) (((DefaultMutableTreeNode) element).getUserObject());
            if (mi.getName().startsWith("Thread")) {
                count++;
            }
        }

        ThreadInfo mi = (ThreadInfo) threadOrMonitorNode.getUserObject();
        if (ThreadDumpInfo.areALotOfWaiting(count)) {
            mi.setALotOfWaiting(true);
        }
        if (isThreadNode) {
            count--;
        }

        mi.setChildCount(count);
        if (count > 1) {
            mi.setName(mi.getName() + ":    " + count + " Blocked threads");
        } else if (count == 1) {
            mi.setName(mi.getName() + ":    " + count + " Blocked thread");
        }

        // Recurse
        for (Enumeration e = threadOrMonitorNode.children(); e.hasMoreElements();) {
            updateChildCount((DefaultMutableTreeNode) e.nextElement(), !isThreadNode);
        }
    }

    /**
     * parses a loggc file stream and reads any found class histograms and adds the to the dump store
     *
     * @param loggcFileStream the stream to read
     * @param root            the root node of the dumps.
     */
    public void parseLoggcFile(InputStream loggcFileStream, DefaultMutableTreeNode root) {
        final BufferedReader bis = new BufferedReader(new InputStreamReader(loggcFileStream));
        final List<HistogramTableModel> histograms = new Vector<>();
        try {
            while (bis.ready()) {
                bis.mark(getMarkSize());
                String nextLine = bis.readLine();
                if (nextLine.startsWith("num   #instances    #bytes  class name")) {
                    bis.reset();
                    histograms.add(parseNextClassHistogram(bis));
                }
            }

            // now add the found histograms to the tree.
            for (int i = histograms.size() - 1; i >= 0; i--) {
                DefaultMutableTreeNode dump = getNextDumpForHistogram(root);
                if (dump != null) {
                    addHistogramToDump(dump, histograms.get(i));
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            IOUtils.closeQuietly(bis);
        }
    }

    /**
     * generate thread info token for table view.
     *
     * @param name the thread info.
     * @return thread tokens.
     */
    public String[] getThreadTokens(String name) {
        String[] tokens;

        if (name.indexOf("prio") > 0) {
            tokens = new String[7];

            tokens[0] = name.substring(1, name.lastIndexOf('"'));
            tokens[1] = name.indexOf("daemon") > 0 ? "Daemon" : "Task";

            String strippedToken = name.substring(name.lastIndexOf('"') + 1);

            if (strippedToken.contains("tid=")) {
                tokens[2] = strippedToken.substring(strippedToken.indexOf("prio=") + 5,
                        strippedToken.indexOf("tid=") - 1);
            } else {
                tokens[2] = strippedToken.substring(strippedToken.indexOf("prio=") + 5);
            }

            if ((strippedToken.contains("tid=")) && (strippedToken.contains("nid="))) {
                tokens[3] = String.valueOf(Long.parseLong(strippedToken.substring(strippedToken.indexOf("tid=") + 6,
                        strippedToken.indexOf("nid=") - 1), 16));
            } else if (strippedToken.contains("tid=")) {
                tokens[3] = String
                        .valueOf(Long.parseLong(strippedToken.substring(strippedToken.indexOf("tid=") + 6), 16));
            }

            // default for token 6 is:
            tokens[6] = "<no address range>";

            if ((strippedToken.contains("nid="))
                    && (strippedToken.indexOf(' ', strippedToken.indexOf("nid="))) >= 0) {
                if (strippedToken.indexOf("nid=0x") > 0) { // is hexadecimal
                    String nidToken = strippedToken.substring(strippedToken.indexOf("nid=") + 6,
                            strippedToken.indexOf(' ', strippedToken.indexOf("nid=")));
                    tokens[4] = String.valueOf(Long.parseLong(nidToken, 16));
                } else { // is decimal
                    String nidToken = strippedToken.substring(strippedToken.indexOf("nid=") + 4,
                            strippedToken.indexOf(' ', strippedToken.indexOf("nid=")));
                    tokens[4] = nidToken;
                }

                if (strippedToken.indexOf('[') > 0) {
                    if (strippedToken.indexOf("lwp_id=") > 0) {
                        tokens[5] = strippedToken.substring(
                                strippedToken.indexOf(' ', strippedToken.indexOf("lwp_id=")) + 1,
                                strippedToken.indexOf('[', strippedToken.indexOf("lwp_id=")) - 1);
                    } else {
                        tokens[5] = strippedToken.substring(
                                strippedToken.indexOf(' ', strippedToken.indexOf("nid=")) + 1,
                                strippedToken.indexOf('[', strippedToken.indexOf("nid=")) - 1);
                    }
                    tokens[6] = strippedToken.substring(strippedToken.indexOf('['));
                } else {
                    tokens[5] = strippedToken
                            .substring(strippedToken.indexOf(' ', strippedToken.indexOf("nid=")) + 1);
                }
            } else if (strippedToken.contains("nid=")) {
                String nidToken = strippedToken.substring(strippedToken.indexOf("nid=") + 6);
                // nid is at the end.
                if (nidToken.indexOf("0x") > 0) { // is hexadecimal
                    tokens[4] = String.valueOf(Long.parseLong(nidToken, 16));
                } else {
                    tokens[4] = nidToken;
                }
            }
        } else {
            tokens = new String[3];
            tokens[0] = name.substring(1, name.lastIndexOf('"'));
            if (name.indexOf("nid=") > 0) {
                tokens[1] = name.substring(name.indexOf("nid=") + 4, name.indexOf("state=") - 1);
                tokens[2] = name.substring(name.indexOf("state=") + 6);
            } else if (name.indexOf("t@") > 0) {
                tokens[1] = name.substring(name.indexOf("t@") + 2, name.indexOf("state=") - 1);
                tokens[2] = name.substring(name.indexOf("state=") + 6);
            } else {
                tokens[1] = name.substring(name.indexOf("id=") + 3, name.indexOf(" in"));
                tokens[2] = name.substring(name.indexOf(" in") + 3);
            }
        }

        return (tokens);
    }

    /**
     * check if the passed logline contains the beginning of a sun jdk thread
     * dump.
     *
     * @param logLine the line of the logfile to test
     * @return true, if the start of a sun thread dump is detected.
     */
    public static boolean checkForSupportedThreadDump(String logLine) {
        return (logLine.trim().contains("Full thread dump"));
    }

    protected String getNextLine() throws IOException {
        return getBis().readLine();
    }
}