blob: a6ef7c7b62e4cc8fb688d12e790fa98c878098ae [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2020 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.mat.internal.snapshot.inspections;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.query.Bytes;
import org.eclipse.mat.query.Column;
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.ISelectionProvider;
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.query.annotations.Menu;
import org.eclipse.mat.query.annotations.Menu.Entry;
import org.eclipse.mat.snapshot.IPathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.util.IProgressListener;
@CommandName("path2gc")
@Icon("/META-INF/icons/path2gc.gif")
@Menu( { @Entry(options = "-excludes \"\""), //
@Entry(options = "-excludes java.lang.ref.WeakReference:referent java.lang.ref.Finalizer:referent java.lang.Runtime:<Unfinalized>"), //
@Entry(options = "-excludes java.lang.ref.SoftReference:referent"), //
@Entry(options = "-excludes java.lang.ref.PhantomReference:referent"), //
@Entry(options = "-excludes java.lang.ref.WeakReference:referent java.lang.ref.Finalizer:referent java.lang.Runtime:<Unfinalized> java.lang.ref.SoftReference:referent"), //
@Entry(options = "-excludes java.lang.ref.PhantomReference:referent java.lang.ref.SoftReference:referent"), //
@Entry(options = "-excludes java.lang.ref.PhantomReference:referent java.lang.ref.WeakReference:referent java.lang.ref.Finalizer:referent java.lang.Runtime:<Unfinalized>"), //
@Entry(options = "-excludes java.lang.ref.Reference:referent java.lang.Runtime:<Unfinalized>") //
})
@HelpUrl("/org.eclipse.mat.ui.help/reference/inspections/path_to_gc_roots.html")
public class Path2GCRootsQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument(flag = Argument.UNFLAGGED, advice = Argument.Advice.HEAP_OBJECT)
public int object;
@Argument(isMandatory = false)
public List<String> excludes = Arrays.asList( //
new String[] { "java.lang.ref.WeakReference:referent", "java.lang.ref.SoftReference:referent" }); //$NON-NLS-1$ //$NON-NLS-2$
@Argument(isMandatory = false)
public int numberOfPaths = 30;
public IResult execute(IProgressListener listener) throws Exception
{
// convert excludes into the required format
Map<IClass, Set<String>> excludeMap = convert(snapshot, excludes);
// create result tree
IPathsFromGCRootsComputer computer = snapshot.getPathsFromGCRoots(object, excludeMap);
Tree result = new Tree(snapshot, object, computer);
result.addInitialPaths(Math.max(1, numberOfPaths), listener);
return result;
}
protected static Map<IClass, Set<String>> convert(ISnapshot snapshot, List<String> excludes)
throws SnapshotException
{
Map<IClass, Set<String>> excludeMap = null;
if (excludes != null && !excludes.isEmpty())
{
excludeMap = new HashMap<IClass, Set<String>>();
for (String entry : excludes)
{
String pattern = entry;
Set<String> fields = null;
int colon = entry.indexOf(':');
if (colon >= 0)
{
fields = new HashSet<String>();
StringTokenizer tokens = new StringTokenizer(entry.substring(colon + 1), ","); //$NON-NLS-1$
while (tokens.hasMoreTokens())
fields.add(tokens.nextToken());
pattern = pattern.substring(0, colon);
}
for (IClass clazz : snapshot.getClassesByName(Pattern.compile(pattern), true))
excludeMap.put(clazz, fields);
}
}
return excludeMap;
}
// //////////////////////////////////////////////////////////////
// inner classes
// //////////////////////////////////////////////////////////////
private static class Node
{
int objectId;
List<Node> children;
String label;
String gcRoots;
Bytes shallowHeap;
Bytes retainedHeap;
boolean isExpanded;
boolean isSelected;
private static final Bytes UNSET = new Bytes(-1);
public Node(int objectId)
{
this.objectId = objectId;
this.shallowHeap = UNSET;
this.retainedHeap = UNSET;
}
/* package */Node getChild(int childId)
{
Node child = null;
if (children == null)
{
children = new ArrayList<Node>();
}
else
{
for (Node c : children)
{
if (c.objectId == childId)
{
child = c;
break;
}
}
}
return child;
}
/* package */Node addChild(int childId)
{
ChildNode child = new ChildNode(this, childId);
children.add(child);
return child;
}
}
private static class ChildNode extends Node
{
Node parent;
String attribute;
private ChildNode(Node parent, int objectId)
{
super(objectId);
this.parent = parent;
}
}
public final static class Tree implements IResultTree, IIconProvider, IDecorator, ISelectionProvider
{
ISnapshot snapshot;
IPathsFromGCRootsComputer computer;
Node root;
int noOfPathsFound;
public Tree(ISnapshot snapshot, int objectId, IPathsFromGCRootsComputer computer)
{
this.snapshot = snapshot;
this.computer = computer;
this.root = new Node(objectId);
this.noOfPathsFound = 0;
}
/**
* @return the ancestors of the first newly created object
*/
public List<?> addNextPath() throws SnapshotException
{
if (computer == null)
return null;
int[] path = computer.getNextShortestPath();
if (path == null)
{
computer = null;
return null;
}
List<Object> ancestors = new ArrayList<Object>();
boolean fatherToBeFound = false;
noOfPathsFound++;
Node current = root;
for (int pos = 1; pos < path.length; pos++)
{
Node child = current.getChild(path[pos]);
if (child == null)
{
child = current.addChild(path[pos]);
if (!fatherToBeFound)
ancestors.add(current);
fatherToBeFound = true;
}
else
{
ancestors.add(current);
}
current = child;
}
return ancestors;
}
public boolean morePathsAvailable()
{
return computer != null;
}
public int getNumberOfPaths()
{
return noOfPathsFound;
}
private void addInitialPaths(int noOfNextPaths, IProgressListener listener) throws SnapshotException
{
if (computer == null)
return;
root.isExpanded = true;
for (int ii = 0; ii < noOfNextPaths; ii++)
{
int[] path = computer.getNextShortestPath();
if (path == null)
{
computer = null;
break;
}
add(path, ii == 0);
if (listener.isCanceled())
return;
}
}
private void add(int[] path, boolean expandAndSelect)
{
noOfPathsFound++;
Node current = root;
for (int pos = 1; pos < path.length; pos++)
{
Node child = current.getChild(path[pos]);
if (child == null)
child = current.addChild(path[pos]);
if (expandAndSelect)
child.isExpanded = true;
current = child;
}
if (expandAndSelect)
current.isSelected = true;
}
public ResultMetaData getResultMetaData()
{
return null;
}
public final Column[] getColumns()
{
return new Column[] { new Column(Messages.Column_ClassName).decorator(this), //
new Column(Messages.Column_ShallowHeap, Bytes.class).noTotals(), //
new Column(Messages.Column_RetainedHeap, Bytes.class).noTotals() };
}
public List<?> getElements()
{
List<Node> answer = new ArrayList<Node>(1);
answer.add(root);
return answer;
}
public List<?> getChildren(Object parent)
{
return ((Node) parent).children;
}
public boolean hasChildren(Object element)
{
return ((Node) element).children != null;
}
public final Object getColumnValue(Object row, int columnIndex)
{
try
{
Node node = (Node) row;
switch (columnIndex)
{
case 0:
if (node.label == null)
{
IObject obj = snapshot.getObject(node.objectId);
node.label = obj.getDisplayName();
node.shallowHeap = new Bytes(obj.getUsedHeapSize());
}
return node.label;
case 1:
if (node.shallowHeap.getValue() == -1)
node.shallowHeap = new Bytes(snapshot.getHeapSize(node.objectId));
return node.shallowHeap;
case 2:
if (node.retainedHeap.getValue() == -1)
node.retainedHeap = new Bytes(snapshot.getRetainedHeapSize(node.objectId));
return node.retainedHeap;
}
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
return null;
}
public final IContextObject getContext(final Object row)
{
return new IContextObject()
{
public int getObjectId()
{
return ((Node) row).objectId;
}
};
}
public URL getIcon(Object row)
{
if (row instanceof ChildNode)
return Icons.inbound(snapshot, ((Node) row).objectId);
else
return Icons.forObject(snapshot, ((Node) row).objectId);
}
public boolean isExpanded(Object row)
{
return ((Node) row).isExpanded;
}
public boolean isSelected(Object row)
{
return ((Node) row).isSelected;
}
public final String prefix(Object row)
{
if (row instanceof ChildNode)
{
ChildNode node = (ChildNode) row;
if (node.attribute == null)
fillInAttribute(node);
return node.attribute;
}
else
{
return null;
}
}
private void fillInAttribute(ChildNode node)
{
try
{
IObject heapObject = snapshot.getObject(node.objectId);
long parentAddress = snapshot.mapIdToAddress(node.parent.objectId);
StringBuilder s = new StringBuilder(64);
List<NamedReference> refs = heapObject.getOutboundReferences();
for (NamedReference reference : refs)
{
if (reference.getObjectAddress() == parentAddress)
{
if (s.length() > 0)
s.append(", "); //$NON-NLS-1$
s.append(reference.getName());
}
}
node.attribute = s.toString();
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
public final String suffix(Object row)
{
try
{
Node node = (Node) row;
if (node.gcRoots == null)
{
if (snapshot.isGCRoot(node.objectId))
node.gcRoots = GCRootInfo.getTypeSetAsString(snapshot.getGCRootInfo(node.objectId));
}
return node.gcRoots;
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
}
}