/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.olivergondza.dumpling.query;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.Nonnull;
import com.github.olivergondza.dumpling.model.ModelObject;
import com.github.olivergondza.dumpling.model.ModelObject.Mode;
import com.github.olivergondza.dumpling.model.ProcessRuntime;
import com.github.olivergondza.dumpling.model.ProcessThread;
import com.github.olivergondza.dumpling.model.ThreadSet;
/**
* Print trees of blocking threads.
*
* @author ogondza
*/
public final class BlockingTree implements SingleThreadSetQuery<BlockingTree.Result<?, ?, ?>> {
private boolean showStackTraces = false;
public BlockingTree showStackTraces() {
this.showStackTraces = true;
return this;
}
/**
* @param threads Only show tree branches that contain threads in this set.
* Provide all threads in runtime to analyze whole runtime.
*/
@Override
public @Nonnull <
SetType extends ThreadSet<SetType, RuntimeType, ThreadType>,
RuntimeType extends ProcessRuntime<RuntimeType, SetType, ThreadType>,
ThreadType extends ProcessThread<ThreadType, SetType, RuntimeType>
> Result<SetType, RuntimeType, ThreadType> query(SetType threads) {
return new Result<SetType, RuntimeType, ThreadType>(threads, showStackTraces);
}
/**
* Forest of all blocking trees found.
*
* @author ogondza
*/
public static final class Result<
SetType extends ThreadSet<SetType, RuntimeType, ThreadType>,
RuntimeType extends ProcessRuntime<RuntimeType, SetType, ThreadType>,
ThreadType extends ProcessThread<ThreadType, SetType, RuntimeType>
> extends SingleThreadSetQuery.Result<SetType, RuntimeType, ThreadType> {
private static final @Nonnull Deadlocks DEADLOCKS = new Deadlocks();
private final @Nonnull Set<Tree<ThreadType>> trees;
private final @Nonnull SetType involved;
private final Deadlocks.Result<SetType, RuntimeType, ThreadType> deadlocks;
private final @Nonnull SetType deadlockedThreads;
/*package*/ Result(@Nonnull SetType threads, boolean showStackTraces) {
super(showStackTraces);
deadlocks = DEADLOCKS.query(threads);
deadlockedThreads = deadlocks.involvedThreads();
@Nonnull Set<Tree<ThreadType>> roots = new LinkedHashSet<Tree<ThreadType>>();
for (ThreadType thread: threads.getProcessRuntime().getThreads()) {
// consider only unblocked threads or possibly deadlocked ones
if (thread.getWaitingToLock() != null && !deadlockedThreads.contains(thread)) continue;
// No thread can be blocked by this
if (thread.getAcquiredLocks().isEmpty()) continue;
// No blocked and not-deadlocked threads to report
if (thread.getBlockedThreads().ignoring(deadlockedThreads).isEmpty()) continue;
roots.add(new Tree<ThreadType>(thread, buildDown(thread)));
}
this.trees = Collections.unmodifiableSet(filter(roots, threads));
LinkedHashSet<ThreadType> involved = new LinkedHashSet<ThreadType>();
for (Tree<ThreadType> root: trees) {
flatten(root, involved);
}
for (SetType deadlock: deadlocks.getDeadlocks()) {
for (ThreadType deadlockedThread: deadlock) {
involved.add(deadlockedThread);
}
}
this.involved = threads.derive(involved);
}
private @Nonnull Set<Tree<ThreadType>> buildDown(ThreadType thread) {
@Nonnull Set<Tree<ThreadType>> newTrees = new HashSet<Tree<ThreadType>>();
for(ThreadType t: thread.getBlockedThreads().ignoring(deadlockedThreads)) {
newTrees.add(new Tree<ThreadType>(t, buildDown(t)));
}
return newTrees;
}
private @Nonnull Set<Tree<ThreadType>> filter(Set<Tree<ThreadType>> roots, SetType threads) {
Set<Tree<ThreadType>> filtered = new LinkedHashSet<Tree<ThreadType>>();
for (Tree<ThreadType> r: roots) {
// Add whitelisted items including their subtrees
if (threads.contains(r.getRoot())) {
filtered.add(r);
}
// Remove nodes with all children filtered out
final Set<Tree<ThreadType>> filteredLeaves = filter(r.getLeaves(), threads);
if (filteredLeaves.isEmpty()) continue;
filtered.add(new Tree<ThreadType>(r.getRoot(), filteredLeaves));
}
return filtered;
}
private void flatten(Tree<ThreadType> tree, Set<ThreadType> accumulator) {
accumulator.add(tree.getRoot());
for (Tree<ThreadType> leaf: tree.getLeaves()) {
flatten(leaf, accumulator);
}
}
/**
* All trees detected.
*
* Empty, when there are no blocked/blocking threads.
*/
public @Nonnull Set<Tree<ThreadType>> getTrees() {
return trees;
}
/**
* Get tree roots, blocking threads that are not blocked.
*
* @since 0.2
*/
public @Nonnull SetType getRoots() {
Set<ThreadType> roots = new LinkedHashSet<ThreadType>(trees.size());
for (Tree<ThreadType> tree: trees) {
roots.add(tree.getRoot());
}
return involved.derive(roots);
}
@Override
protected void printResult(PrintStream out) {
for (Tree<ThreadType> tree: trees) {
tree.toString(out, Mode.HUMAN);
out.println();
}
if (!deadlocks.getDeadlocks().isEmpty()) {
out.println();
deadlocks.printResult(out);
}
}
@Override
protected SetType involvedThreads() {
return involved;
}
@Override
protected void printSummary(PrintStream out) {
out.printf("All threads: %d; Roots: %d", involved.size(), trees.size());
if (!deadlocks.getDeadlocks().isEmpty()) {
out.print(' ');
deadlocks.printSummary(out);
} else {
out.println();
}
}
}
/**
* Blocking tree node.
*
* A <tt>root</tt> with directly blocked subtrees (<tt>leaves</tt>). If
* leave set is empty root thread does not block any other threads.
*
* @author ogondza
*/
public final static class Tree<ThreadType extends ProcessThread<ThreadType, ?, ?>> extends ModelObject {
private final @Nonnull ThreadType root;
private final @Nonnull Set<Tree<ThreadType>> leaves;
private Tree(@Nonnull ThreadType root, @Nonnull Set<Tree<ThreadType>> leaves) {
this.root = root;
this.leaves = Collections.unmodifiableSet(leaves);
}
/*package*/ Tree(@Nonnull ThreadType root, @Nonnull Tree<ThreadType>... leaves) {
this(root, new LinkedHashSet<Tree<ThreadType>>(Arrays.asList(leaves)));
}
public @Nonnull ThreadType getRoot() {
return root;
}
public @Nonnull Set<Tree<ThreadType>> getLeaves() {
return leaves;
}
@Override
public void toString(PrintStream stream, Mode mode) {
writeInto("", stream, mode);
}
private void writeInto(String prefix, PrintStream sb, Mode mode) {
sb.append(prefix).append(root.getHeader()).println();
for (Tree<ThreadType> l: leaves) {
l.writeInto(prefix + "\t", sb, mode);
}
}
@Override
public int hashCode() {
int hashCode = 31 * root.hashCode();
for (Tree<ThreadType> l: leaves) {
hashCode += l.hashCode() * 7;
}
return hashCode;
}
@Override
public boolean equals(Object rhs) {
if (rhs == null) return false;
if (rhs == this) return true;
if (!rhs.getClass().equals(this.getClass())) return false;
@SuppressWarnings("unchecked")
Tree<ThreadType> other = (Tree<ThreadType>) rhs;
return this.root.equals(other.root) && this.leaves.equals(other.leaves);
}
}
}