blob: 1142b186f28c809ff978169e15ac541ee0ba3282 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP AG - initial API and implementation
* IBM Corporation - optional columns, cancellation
*******************************************************************************/
package org.eclipse.mat.inspections.threads;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.inspections.InspectionAssert;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.DetailResultProvider;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IDecorator;
import org.eclipse.mat.query.IIconProvider;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.ResultMetaData;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.CommandName;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.extension.Subject;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.GCRootInfo.Type;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IStackFrame;
import org.eclipse.mat.snapshot.model.IThreadStack;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.model.ThreadToLocalReference;
import org.eclipse.mat.snapshot.query.IHeapObjectArgument;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.snapshot.query.ObjectListResult;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
import org.eclipse.mat.util.IProgressListener;
@CommandName("thread_overview")
@Icon("/META-INF/icons/threads.gif")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/analyzingthreads.html")
@Subject("java.lang.Thread")
public class ThreadOverviewQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument(isMandatory = false, flag = Argument.UNFLAGGED)
public IHeapObjectArgument objects;
private static URL THREAD_ICON_URL = Icons.getURL("thread.gif"); //$NON-NLS-1$
private static URL STACK_FRAME_ICON_URL = Icons.getURL("stack_frame.gif"); //$NON-NLS-1$
public static final String CLASS_THREAD = "java.lang.Thread"; //$NON-NLS-1$
public IResult execute(IProgressListener listener) throws Exception
{
listener.subTask(Messages.ThreadOverviewQuery_SearchingThreads);
List<ThreadOverviewNode> result = new ArrayList<ThreadOverviewNode>();
if (objects != null)
{
for (int[] objectIds : objects)
{
for (int objectId : objectIds)
{
if (listener.isCanceled())
break;
if (isThread(snapshot, objectId)) {
result.add(buildThreadOverviewNode(objectId, listener));
}
}
}
}
else
{
Collection<IClass> classes = snapshot.getClassesByName(CLASS_THREAD, true);
if (classes != null)
{
for (IClass clasz : classes)
{
for (int id : clasz.getObjectIds())
{
if (listener.isCanceled())
break;
result.add(buildThreadOverviewNode(id, listener));
}
}
}
// Add objects marked as a GCRoot THREAD_OBJ
for (int objectId : snapshot.getGCRoots())
{
GCRootInfo gcs[] = snapshot.getGCRootInfo(objectId);
if (gcs != null)
{
for (GCRootInfo gc : gcs)
{
// Sometime PHD files have threads marked as objects
if (gc.getType() == Type.THREAD_OBJ)
{
if (isThread(snapshot, objectId)) {
result.add(buildThreadOverviewNode(objectId, listener));
}
}
}
}
if (listener.isCanceled())
break;
}
}
Collections.sort(result, new Comparator<ThreadOverviewNode>()
{
public int compare(ThreadOverviewNode o1, ThreadOverviewNode o2)
{
return o1.threadInfo.getRetainedHeap().getValue() > o2.threadInfo.getRetainedHeap().getValue() ? -1
: o1.threadInfo.getRetainedHeap().getValue() < o2.threadInfo.getRetainedHeap().getValue() ? 1 :
Long.compare(o1.threadInfo.getThreadObject().getObjectAddress(), o2.threadInfo.getThreadObject().getObjectAddress());
}
});
// Remove duplicates - easy as already sorted
ThreadOverviewNode prev = null;
for (Iterator<ThreadOverviewNode>it = result.iterator(); it.hasNext();)
{
ThreadOverviewNode node = it.next();
if (prev != null && node.threadInfo.getThreadId() == prev.threadInfo.getThreadId())
{
it.remove();
}
else
{
prev = node;
}
}
if (result.isEmpty())
return null;
return new ThreadInfoList(snapshot, result);
}
public static boolean isThread(ISnapshot snapshot, int objectId) throws SnapshotException
{
IObject obj = snapshot.getObject(objectId);
IClass cls = obj.getClazz();
if (cls != null)
{
String className = cls.getName();
if (CLASS_THREAD.equals(className))
{
return true;
}
while (cls.hasSuperClass())
{
cls = cls.getSuperClass();
className = cls.getName();
if (CLASS_THREAD.equals(className))
{
return true;
}
}
GCRootInfo gcs[] = snapshot.getGCRootInfo(objectId);
if (gcs != null)
{
// Reset class name
className = obj.getClazz().getName();
for (GCRootInfo gc : gcs)
{
// Sometime PHD files have threads marked as objects
if (gc.getType() == Type.THREAD_OBJ && "java.lang.Object".equals(className)) //$NON-NLS-1$
return true;
}
}
}
return false;
}
private ThreadOverviewNode buildThreadOverviewNode(int objectId, IProgressListener listener) throws SnapshotException
{
ThreadOverviewNode result = new ThreadOverviewNode();
result.threadInfo = ThreadInfoImpl.build(snapshot.getObject(objectId), false, listener);
result.stack = snapshot.getThreadStack(objectId);
// add all GC roots referenced from this thread stack
if (result.stack != null)
{
IStackFrame[] frames = result.stack.getStackFrames();
if (frames != null)
{
SetInt roots = new SetInt();
for (IStackFrame IStackFrame : frames)
{
int[] objects = IStackFrame.getLocalObjectsIds();
if (objects != null)
{
for (int i : objects)
{
roots.add(i);
}
}
}
result.stackRoots = roots.toArray();
}
}
return result;
}
private class ThreadOverviewNode
{
private ThreadInfoImpl threadInfo;
private IThreadStack stack;
private int[] stackRoots;
}
private static class ThreadStackFrameNode
{
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + depth;
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ThreadStackFrameNode other = (ThreadStackFrameNode) obj;
if (depth != other.depth)
return false;
return true;
}
private ThreadOverviewNode threadOverviewNode;
private IStackFrame stackFrame;
private int depth;
private boolean firstNonNativeFrame;
private int objectId;
}
private static class ThreadStackFrameLocalNode
{
private ThreadStackFrameNode threadStackFrameNode;
private int objectId;
}
private static class ThreadInfoList implements IResultTree, IIconProvider, IDecorator
{
ISnapshot snapshot;
List<ThreadOverviewNode> infos;
Column[] columns;
HashMapIntObject<Object> root2element;
ObjectListResult.Outbound objectList;
int colMap[];
public ThreadInfoList(ISnapshot snapshot, List<ThreadOverviewNode> infos)
{
this.snapshot = snapshot;
this.infos = infos;
List<ThreadInfoImpl> threadInfos = new ArrayList<ThreadInfoImpl>(infos.size());
ArrayInt roots = new ArrayInt();
for (ThreadOverviewNode node : infos)
{
threadInfos.add(node.threadInfo);
if (node.stackRoots != null) {
roots.addAll(node.stackRoots);
}
}
this.columns = ThreadInfoImpl.getUsedColumns(threadInfos).toArray(new Column[0]);
this.columns[0].decorator(this).comparing(new NoCompareComparator());
root2element = new HashMapIntObject<Object>();
objectList = new ObjectListResult.Outbound(snapshot, roots.toArray());
for (Object o : objectList.getElements())
root2element.put(objectList.getContext(o).getObjectId(), o);
// Find matching columns in the object list
colMap = new int[columns.length];
Column[] objColumns = objectList.getColumns();
for (int i = 0; i < columns.length; ++i)
{
colMap[i] = i == 0 ? 0 : -1;
for (int j = 0; j < objColumns.length; ++j)
{
Column threadCol = columns[i];
Column listCol = objColumns[j];
// Don't worry about matching totals or sort direction.
if (threadCol.getLabel().equals(listCol.getLabel())
&& Objects.equals(threadCol.getFormatter(), listCol.getFormatter())
&& threadCol.getType().equals(listCol.getType()))
{
colMap[i] = j;
break;
}
}
}
}
public ResultMetaData getResultMetaData()
{
return new ResultMetaData.Builder() //
.addDetailResult(new DetailResultProvider(Messages.ThreadOverviewQuery_ThreadDetails)
{
@Override
public boolean hasResult(Object row)
{
if (!(row instanceof ThreadOverviewNode))
return false;
try
{
InspectionAssert.heapFormatIsNot(snapshot, "phd"); //$NON-NLS-1$
return true;
}
catch (UnsupportedOperationException e)
{
return false;
}
}
@Override
public URL getIcon()
{
try
{
return SnapshotQuery
.lookup("thread_details", snapshot).getDescriptor().getIcon(); //$NON-NLS-1$
}
catch (SnapshotException e)
{
return null;
}
}
@Override
public IResult getResult(Object row, IProgressListener listener)
throws SnapshotException
{
int threadId = ((ThreadOverviewNode) row).threadInfo.getThreadId();
return SnapshotQuery.lookup("thread_details", snapshot) //$NON-NLS-1$
.setArgument("threadIds", threadId) //$NON-NLS-1$
.execute(listener);
}
}) //
.build();
}
public Column[] getColumns()
{
return columns;
}
public Object getColumnValue(Object row, int columnIndex)
{
if (row instanceof ThreadOverviewNode) {
ThreadOverviewNode info = (ThreadOverviewNode) row;
return info.threadInfo.getValue(columns[columnIndex]);
} else {
if (row instanceof ThreadStackFrameNode)
{
switch (columnIndex)
{
case 0:
IStackFrame frame = ((ThreadStackFrameNode) row).stackFrame;
return frame.getText();
default:
break;
}
}
else
{
int newColumnIndex = colMap[columnIndex];
if (newColumnIndex >= 0)
{
if (row instanceof ThreadStackFrameLocalNode)
{
row = root2element.get(((ThreadStackFrameLocalNode)row).objectId);
}
return objectList.getColumnValue(row, newColumnIndex);
}
}
return null;
}
}
public IContextObject getContext(final Object row)
{
if (row instanceof ThreadOverviewNode) {
return new IContextObject()
{
public int getObjectId()
{
return ((ThreadOverviewNode) row).threadInfo.getThreadId();
}
};
}
if (row instanceof ThreadStackFrameNode)
{
if (((ThreadStackFrameNode)row).objectId != -1)
{
return new IContextObject()
{
public int getObjectId()
{
return ((ThreadStackFrameNode) row).objectId;
}
};
}
return null;
}
if (row instanceof ThreadStackFrameLocalNode)
{
return objectList.getContext(root2element.get(((ThreadStackFrameLocalNode)row).objectId));
}
else
{
return objectList.getContext(row);
}
}
public URL getIcon(Object row)
{
if (row instanceof ThreadOverviewNode)
{
return THREAD_ICON_URL;
}
else if (row instanceof ThreadStackFrameNode)
{
return STACK_FRAME_ICON_URL;
}
else
{
if (row instanceof ThreadStackFrameLocalNode)
{
row = root2element.get(((ThreadStackFrameLocalNode)row).objectId);
}
return objectList.getIcon(row);
}
}
public String prefix(Object row)
{
if (row instanceof ThreadOverviewNode)
return null;
else if (row instanceof ThreadStackFrameNode)
return null;
else
{
ThreadStackFrameLocalNode tsfmln = null;
if (row instanceof ThreadStackFrameLocalNode)
{
tsfmln = (ThreadStackFrameLocalNode) row;
row = root2element.get(tsfmln.objectId);
}
String prefix = objectList.prefix(row);
if (prefix == null)
{
// If this is the top stack frame and this object is a GC
// Root, then check if it's of the type BUSY_MONITOR with
// the context ID of this thread, and if so, note that this
// is the object the thread is blocked on
if (tsfmln != null && tsfmln.threadStackFrameNode != null)
{
boolean topFrame = tsfmln.threadStackFrameNode.firstNonNativeFrame || tsfmln.threadStackFrameNode.depth == 0;
ThreadInfoImpl threadInfo = tsfmln.threadStackFrameNode.threadOverviewNode.threadInfo;
int objectId = objectList.getContext(row).getObjectId();
try
{
GCRootInfo[] gcRootInfos = snapshot.getGCRootInfo(objectId);
if (gcRootInfos != null)
{
for (GCRootInfo gcRootInfo : gcRootInfos)
{
if (gcRootInfo.getType() == GCRootInfo.Type.BUSY_MONITOR
&& gcRootInfo.getContextAddress() != 0
&& gcRootInfo.getContextId() == threadInfo.getThreadId())
{
return topFrame ? Messages.ThreadStackQuery_Label_Local_Probably_Blocked_On
: Messages.ThreadStackQuery_Label_Local_Busy_Monitor;
}
if (gcRootInfo.getType() == GCRootInfo.Type.BUSY_MONITOR
&& gcRootInfo.getContextAddress() == 0)
{
// HPROF doesn't have a context address
return topFrame ? Messages.ThreadStackQuery_Label_Local_Possibly_Blocked_On
: Messages.ThreadStackQuery_Label_Local_Possible_Busy_Monitor;
}
}
}
List<NamedReference> refs = threadInfo
.getThreadObject().getOutboundReferences();
for (NamedReference ref : refs)
{
if (ref.getObjectId() != objectId)
continue;
if (ref instanceof ThreadToLocalReference)
{
ThreadToLocalReference tlr = (ThreadToLocalReference)ref;
for (GCRootInfo gcRootInfo : tlr.getGcRootInfo())
{
if (gcRootInfo.getType() == GCRootInfo.Type.BUSY_MONITOR)
{
return topFrame ? Messages.ThreadStackQuery_Label_Local_Probably_Blocked_On
: Messages.ThreadStackQuery_Label_Local_Busy_Monitor;
}
}
}
}
}
catch (SnapshotException e)
{
// Something is wrong with the GC root information,
// so just skip this
}
}
return Messages.ThreadStackQuery_Label_Local;
}
else
{
return prefix;
}
}
}
public String suffix(Object row)
{
if (row instanceof ThreadOverviewNode)
return null;
else if (row instanceof ThreadStackFrameNode)
return null;
else
{
if (row instanceof ThreadStackFrameLocalNode)
{
row = root2element.get(((ThreadStackFrameLocalNode)row).objectId);
}
return objectList.suffix(row);
}
}
public List<?> getElements()
{
return infos;
}
public boolean hasChildren(Object element)
{
if (element instanceof ThreadOverviewNode)
{
IThreadStack stack = ((ThreadOverviewNode) element).stack;
if (stack == null) return false;
IStackFrame[] frames = stack.getStackFrames();
return frames != null && frames.length > 0;
}
else if (element instanceof ThreadStackFrameNode)
{
IStackFrame frame = ((ThreadStackFrameNode) element).stackFrame;
int[] objectIds = frame.getLocalObjectsIds();
return objectIds != null && objectIds.length > 0;
}
else
{
if (element instanceof ThreadStackFrameLocalNode)
{
element = root2element.get(((ThreadStackFrameLocalNode)element).objectId);
}
return objectList.hasChildren(element);
}
}
public List<?> getChildren(Object parent)
{
if (parent instanceof ThreadOverviewNode)
{
ThreadOverviewNode ton = (ThreadOverviewNode) parent;
IStackFrame[] frames = ton.stack.getStackFrames();
final int numFrames = frames.length;
List<ThreadStackFrameNode> stackFrameNodes = new ArrayList<ThreadStackFrameNode>(numFrames);
boolean foundNonNativeFrame = false;
int frameIds[] = frameIds(ton);
for (int i = 0; i < numFrames; i++)
{
IStackFrame frame = frames[i];
ThreadStackFrameNode tsfn = new ThreadStackFrameNode();
tsfn.stackFrame = frame;
tsfn.depth = i;
tsfn.firstNonNativeFrame = false;
if (!foundNonNativeFrame)
{
String frameText = frame.getText();
if (frameText != null && !frameText.contains("Native Method")) //$NON-NLS-1$
{
tsfn.firstNonNativeFrame = true;
foundNonNativeFrame = true;
}
}
tsfn.threadOverviewNode = ton;
tsfn.objectId = frameIds[tsfn.depth];
if (tsfn.objectId == -1)
{
// Add the class from the class name of the stack frame
tsfn.objectId = frameId(frame);
}
stackFrameNodes.add(tsfn);
}
return stackFrameNodes;
}
else if (parent instanceof ThreadStackFrameNode)
{
ThreadStackFrameNode tsfn = (ThreadStackFrameNode) parent;
IStackFrame frame = tsfn.stackFrame;
int[] localIds = frame.getLocalObjectsIds();
if (localIds != null)
{
List<ThreadStackFrameLocalNode> stackFrameLocals = new ArrayList<ThreadStackFrameLocalNode>(localIds.length);
for (int localId : localIds)
{
ThreadStackFrameLocalNode tsfln = new ThreadStackFrameLocalNode();
tsfln.objectId = localId;
tsfln.threadStackFrameNode = tsfn;
stackFrameLocals.add(tsfln);
}
return stackFrameLocals;
}
else
{
return null;
}
}
else
{
if (parent instanceof ThreadStackFrameLocalNode)
{
parent = root2element.get(((ThreadStackFrameLocalNode)parent).objectId);
}
return objectList.getChildren(parent);
}
}
private int[] frameIds(ThreadOverviewNode ton)
{
final int numFrames = ton.stack.getStackFrames().length;
int frameIds[] = new int[numFrames];
Arrays.fill(frameIds, -1);
IObject to = ton.threadInfo.getThreadObject();
for (NamedReference ref : to.getOutboundReferences())
{
if (ref instanceof ThreadToLocalReference)
{
ThreadToLocalReference tlr = (ThreadToLocalReference)ref;
for (GCRootInfo gcRootInfo : tlr.getGcRootInfo())
{
if (gcRootInfo.getType() == GCRootInfo.Type.JAVA_STACK_FRAME)
{
try
{
Object fn = tlr.getObject().resolveValue("frameNumber"); //$NON-NLS-1$
if (fn instanceof Integer)
{
int f = (Integer)fn;
if (f >= 0 && f < numFrames)
{
frameIds[f] = tlr.getObjectId();
}
}
}
catch (SnapshotException e)
{
// Ignore
}
}
}
}
}
return frameIds;
}
/**
* Return the id of the class associated with this stack frame.
* @param frm
* @return the id, or -1 if not found
*/
private int frameId(IStackFrame frm)
{
String fm = frm.getText();
if (fm != null)
{
// Extract the class name from the full method name
// at package.name.Classname.method(Arguments) Classname.java(13)
fm = fm.replaceFirst("\\s*at\\s+([^(]+)\\.[^.(]+.*", "$1"); //$NON-NLS-1$//$NON-NLS-2$
try
{
Collection<IClass>clss = snapshot.getClassesByName(fm, false);
// If unambiguous which class
if (clss != null && clss.size() == 1)
{
for (IClass cls : clss)
{
return cls.getObjectId();
}
}
}
catch (SnapshotException e)
{
}
}
return -1;
}
class NoCompareComparator implements Comparator<Object>
{
public int compare(Object o1, Object o2)
{
return 0;
}
}
}
}