blob: da9aab31fa64791b4acc8cdb3d0fa557d53828e0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2023 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
* Andrew Johnson - improve progress monitor checking
*******************************************************************************/
package org.eclipse.mat.inspections;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayIntBig;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.inspections.FindLeaksQuery.AccumulationPoint;
import org.eclipse.mat.inspections.FindLeaksQuery.ExcludesConverter;
import org.eclipse.mat.inspections.FindLeaksQuery.SuspectRecord;
import org.eclipse.mat.inspections.FindLeaksQuery.SuspectRecordGroupOfObjects;
import org.eclipse.mat.inspections.threads.ThreadInfoQuery;
import org.eclipse.mat.inspections.util.ObjectTreeFactory;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.internal.snapshot.inspections.MultiplePath2GCRootsQuery;
import org.eclipse.mat.query.BytesFormat;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IContextObjectSet;
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.annotations.Argument;
import org.eclipse.mat.query.annotations.Argument.Advice;
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.refined.RefinedResultBuilder;
import org.eclipse.mat.query.refined.RefinedTree;
import org.eclipse.mat.query.registry.Converters;
import org.eclipse.mat.query.results.CompositeResult;
import org.eclipse.mat.query.results.ListResult;
import org.eclipse.mat.query.results.TextResult;
import org.eclipse.mat.report.ITestResult;
import org.eclipse.mat.report.Params;
import org.eclipse.mat.report.QuerySpec;
import org.eclipse.mat.report.SectionSpec;
import org.eclipse.mat.snapshot.ClassHistogramRecord;
import org.eclipse.mat.snapshot.Histogram;
import org.eclipse.mat.snapshot.IMultiplePathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsClassRecord;
import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsRecord;
import org.eclipse.mat.snapshot.extension.IThreadInfo;
import org.eclipse.mat.snapshot.extension.ITroubleTicketResolver;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IClassLoader;
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.Icons;
import org.eclipse.mat.snapshot.query.ObjectListResult;
import org.eclipse.mat.snapshot.query.PieFactory;
import org.eclipse.mat.snapshot.query.RetainedSizeDerivedData;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
import org.eclipse.mat.snapshot.registry.TroubleTicketResolverRegistry;
import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
import com.ibm.icu.text.NumberFormat;
@CommandName("leakhunter")
@Icon("/META-INF/icons/leak.gif")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/runningleaksuspectreport.html")
public class LeakHunterQuery implements IQuery
{
static final String SYSTEM_CLASSLOADER = Messages.LeakHunterQuery_SystemClassLoader;
// Use per-instance formatters to avoid thread safety problems
NumberFormat percentFormatter;
{
// Use com.ibm.icu
percentFormatter = NumberFormat.getPercentInstance();
percentFormatter.setMinimumFractionDigits(2);
percentFormatter.setMaximumFractionDigits(2);
}
NumberFormat numberFormatter = NumberFormat.getNumberInstance();
BytesFormat bytesFormatter = BytesFormat.getInstance();
@Argument
public ISnapshot snapshot;
@Argument(isMandatory = false)
public int threshold_percent = 10;
@Argument(isMandatory = false)
public int max_paths = 10000;
@Argument(isMandatory = false, advice = Advice.CLASS_NAME_PATTERN, flag = "skip")
public Pattern skipPattern = Pattern.compile("java\\..*|javax\\..*|com\\.sun\\..*|sun\\..*|jdk\\..*"); //$NON-NLS-1$
@Argument(isMandatory = false)
public List<String> excludes = Arrays.asList( //
new String[] { "java.lang.ref.Reference:referent", "java.lang.ref.Finalizer:unfinalized", "java.lang.Runtime:" + "<" + GCRootInfo.getTypeAsString(GCRootInfo.Type.UNFINALIZED) + ">" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
private long totalHeap;
public IResult execute(IProgressListener listener) throws Exception
{
totalHeap = snapshot.getSnapshotInfo().getUsedHeapSize();
/**
* Find Leaks
* remainder
*/
SimpleMonitor monitor = new SimpleMonitor(
Messages.LeakHunterQuery_ProgressName, listener,
new int[] { 30, 70 });
/* call find_leaks */
listener.subTask(Messages.LeakHunterQuery_FindingProblemSuspects);
FindLeaksQuery.SuspectsResultTable findLeaksResult = callFindLeaks(monitor.nextMonitor());
SuspectRecord[] leakSuspects = findLeaksResult.getData();
SectionSpec result = new SectionSpec(Messages.LeakHunterQuery_LeakHunter);
listener.subTask(Messages.LeakHunterQuery_PreparingResults);
if (leakSuspects.length > 0)
{
/**
* Find Leaks
*/
int ticks2[] = new int[leakSuspects.length + 1];
for (int i = 0; i < leakSuspects.length - 1; ++i)
{
ticks2[i] = 100;
}
ticks2[leakSuspects.length] = 50;
SimpleMonitor monitor2 = new SimpleMonitor(Messages.LeakHunterQuery_ProgressName, monitor.nextMonitor(),
ticks2);
PieFactory pie = new PieFactory(snapshot);
for (int num = 0; num < leakSuspects.length; num++)
{
SuspectRecord rec = leakSuspects[num];
pie.addSlice(rec.suspect.getObjectId(), MessageUtil.format(Messages.LeakHunterQuery_ProblemSuspect, (num + 1)), rec.suspect.getUsedHeapSize(),
rec.getSuspectRetained());
}
result.add(new QuerySpec(Messages.LeakHunterQuery_Overview, pie.build()));
HashMap<Integer, List<Integer>> accPoint2ProblemNr = new HashMap<Integer, List<Integer>>();
int problemNum = 0;
for (SuspectRecord rec : leakSuspects)
{
problemNum++;
AccumulationPoint ap = rec.getAccumulationPoint();
if (ap != null)
{
List<Integer> numbers = accPoint2ProblemNr.get(ap.getObject().getObjectId());
if (numbers == null)
{
numbers = new ArrayList<Integer>(2);
accPoint2ProblemNr.put(ap.getObject().getObjectId(), numbers);
}
numbers.add(problemNum);
}
CompositeResult suspectDetails = getLeakSuspectDescription(rec, monitor2.nextMonitor());
suspectDetails.setStatus(ITestResult.Status.ERROR);
QuerySpec spec = new QuerySpec(MessageUtil.format(Messages.LeakHunterQuery_ProblemSuspect, problemNum));
spec.setResult(suspectDetails);
spec.set(Params.Rendering.PATTERN, Params.Rendering.PATTERN_OVERVIEW_DETAILS);
spec.set(Params.Html.IS_IMPORTANT, Boolean.TRUE.toString());
result.add(spec);
}
// give hints for problems which could be related
List<CompositeResult> hints = findCommonPathForSuspects(accPoint2ProblemNr, monitor2.nextMonitor());
for (int k = 0; k < hints.size(); k++)
{
QuerySpec spec = new QuerySpec(MessageUtil.format(Messages.LeakHunterQuery_Hint, (k + 1)));
spec.setResult(hints.get(k));
spec.set(Params.Rendering.PATTERN, Params.Rendering.PATTERN_OVERVIEW_DETAILS);
spec.set(Params.Html.IS_IMPORTANT, Boolean.TRUE.toString());
result.add(spec);
}
}
listener.done();
if (result.getChildren().size() != 0)
{
return result;
}
else
{
return new TextResult(Messages.LeakHunterQuery_NothingFound);
}
}
FindLeaksQuery.SuspectsResultTable callFindLeaks(IProgressListener listener) throws Exception
{
return (FindLeaksQuery.SuspectsResultTable) SnapshotQuery.lookup("find_leaks", snapshot) //$NON-NLS-1$
.setArgument("threshold_percent", threshold_percent) //$NON-NLS-1$
.setArgument("max_paths", max_paths) //$NON-NLS-1$
.setArgument("excludes", excludes) //$NON-NLS-1$
.execute(listener);
}
private boolean isThreadRelated(SuspectRecord suspect) throws SnapshotException
{
return isThread(suspect.getSuspect().getObjectId());
}
private boolean isThread(int objectId) throws SnapshotException
{
GCRootInfo[] gcRootInfo = snapshot.getGCRootInfo(objectId);
if (gcRootInfo != null)
{
for (GCRootInfo singleInfo : gcRootInfo)
{
if (singleInfo.getType() == GCRootInfo.Type.THREAD_OBJ) { return true; }
}
}
return false;
}
/**
* Is this object a pseudo-object for
* stack frames as objects?
*/
private boolean isStackFrame(int objectId) throws SnapshotException
{
if (snapshot.getClassOf(objectId).doesExtend("<method>") || //$NON-NLS-1$
snapshot.getClassOf(objectId).doesExtend("<stack frame>")) //$NON-NLS-1$
return true;
return false;
}
/**
* Is this object a local variable from the frameId?
* @param frameId the stack frame ID, or thread ID if no stack frames as variables.
* @param objectId the object in questions
* @return true if a local variable
* @throws SnapshotException
*/
private boolean isStackFrameLocal(int frameId, int objectId) throws SnapshotException
{
IObject frame = snapshot.getObject(frameId);
List<NamedReference> refs = frame.getOutboundReferences();
for (NamedReference ref : refs)
{
if (ref instanceof ThreadToLocalReference)
{
if (ref.getObjectId() == objectId)
return true;
}
}
return false;
}
private CompositeResult getLeakSuspectDescription(SuspectRecord suspect, IProgressListener listener)
throws SnapshotException
{
if (suspect instanceof SuspectRecordGroupOfObjects)
{
return getLeakDescriptionGroupOfObjects((SuspectRecordGroupOfObjects) suspect, listener);
}
else
{
return getLeakDescriptionSingleObject(suspect, listener);
}
}
private CompositeResult getLeakDescriptionSingleObject(SuspectRecord suspect, IProgressListener listener)
throws SnapshotException
{
int subq = 5;
int ticks[] = new int[subq];
Arrays.fill(ticks, 100);
SimpleMonitor monitor = new SimpleMonitor(Messages.LeakHunterQuery_DescriptionSingleObject, listener, ticks);
StringBuilder overview = new StringBuilder(256);
TextResult overviewResult = new TextResult(); // used to create links
// from it to other
// results
Set<String> keywords = new LinkedHashSet<String>();
List<IObject> objectsForTroubleTicketInfo = new ArrayList<IObject>(2);
int suspectId = suspect.getSuspect().getObjectId();
/* get dominator info */
boolean isThreadRelated = isThreadRelated(suspect);
if (isThreadRelated) // a thread bound problem
{
overview.append("<p>"); //$NON-NLS-1$
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
HTMLUtils.escapeText(suspect.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
overview.append("</p>"); //$NON-NLS-1$
}
else if (snapshot.isClassLoader(suspectId))
{
IClassLoader suspectClassloader = (IClassLoader) suspect.getSuspect();
objectsForTroubleTicketInfo.add(suspectClassloader);
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ClassLoader, //
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
}
else if (snapshot.isClass(suspectId))
{
String className = ((IClass) suspect.getSuspect()).getName();
keywords.add(className);
IClassLoader suspectClassloader = (IClassLoader) snapshot.getObject(((IClass) suspect.getSuspect())
.getClassLoaderId());
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(suspect.getSuspect());
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Class, //
HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
}
else
{
String className = suspect.getSuspect().getClazz().getName();
keywords.add(className);
IClassLoader suspectClassloader = (IClassLoader) snapshot.getObject(suspect.getSuspect().getClazz()
.getClassLoaderId());
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(suspect.getSuspect());
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Instance, //
HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
/*
* if the class name matches the skip pattern, try to find the first
* referrer which does not match the pattern
*/
if (skipPattern.matcher(className).matches() && !isThreadRelated)
{
int referrerId = findReferrer(suspect.getSuspect().getObjectId());
if (referrerId != -1)
{
IObject referrer = snapshot.getObject(referrerId);
IObject referrerClassloader = null;
if (snapshot.isClassLoader(referrerId))
{
referrerClassloader = referrer;
objectsForTroubleTicketInfo.add(referrerClassloader);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedBy,
referrerClassloaderName));
}
else if (snapshot.isClass(referrerId))
{
className = ((IClass)referrer).getName();
keywords.add(className);
referrerClassloader = snapshot.getObject(((IClass) referrer).getClassLoaderId());
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(referrer);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByClass, HTMLUtils.escapeText(className),
referrerClassloaderName));
}
else
{
if (isThread(referrerId))
{
isThreadRelated = true;
suspectId = referrerId;
IObject suspectObject = snapshot.getObject(suspectId);
suspect = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), suspect.getAccumulationPoint());
}
className = referrer.getClazz().getName();
keywords.add(className);
referrerClassloader = snapshot.getObject(referrer.getClazz().getClassLoaderId());
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(referrer);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByInstance, HTMLUtils.escapeText(referrer
.getDisplayName()), referrerClassloaderName));
if (isThreadRelated)
{
overview.append("<p>"); //$NON-NLS-1$
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
HTMLUtils.escapeText(suspect.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
overview.append("</p>"); //$NON-NLS-1$
}
}
}
}
}
/* get accumulation point info */
if (suspect.getAccumulationPoint() != null)
{
IObject accumulationObject = suspect.getAccumulationPoint().getObject();
int accumulationPointId = accumulationObject.getObjectId();
if (snapshot.isClassLoader(accumulationPointId))
{
IClassLoader accPointClassloader = (IClassLoader) accumulationObject;
objectsForTroubleTicketInfo.add(accPointClassloader);
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedBy, classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else if (snapshot.isClass(accumulationPointId))
{
IClass clazz = (IClass) accumulationObject;
keywords.add(clazz.getName());
IClassLoader accPointClassloader = (IClassLoader) snapshot.getObject(clazz.getClassLoaderId());
// involvedClassloaders.add(accPointClassloader);
objectsForTroubleTicketInfo.add(accumulationObject);
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByLoadedBy, HTMLUtils.escapeText(clazz.getName()),
classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else
{
String className = accumulationObject.getClazz().getName();
keywords.add(className);
IClassLoader accPointClassloader = (IClassLoader) snapshot.getObject(accumulationObject.getClazz()
.getClassLoaderId());
// involvedClassloaders.add(accPointClassloader);
objectsForTroubleTicketInfo.add(accumulationObject);
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByInstance, HTMLUtils.escapeText(className),
classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
}
/* extract request information for thread related problems */
ThreadInfoQuery.Result threadDetails = null;
IObject threadObj = null;
if (isThreadRelated)
{
threadDetails = extractThreadData(suspect, keywords, objectsForTroubleTicketInfo, overview, overviewResult, monitor.nextMonitor());
threadObj = suspect.getSuspect();
}
IObject describedObject = (suspect.getAccumulationPoint() != null) ? suspect.getAccumulationPoint().getObject()
: suspect.getSuspect();
// add a path to the accumulation point
QuerySpec qspath;
try
{
IResult result = SnapshotQuery.lookup("path2gc", snapshot) //$NON-NLS-1$
.setArgument("object", describedObject) //$NON-NLS-1$
.setArgument("excludes", excludes) //$NON-NLS-1$
.execute(monitor.nextMonitor());
qspath = new QuerySpec(Messages.LeakHunterQuery_ShortestPaths, result);
StringBuilder sb = new StringBuilder("path2gc"); //$NON-NLS-1$
sb.append(" 0x").append(Long.toHexString(describedObject.getObjectAddress())); //$NON-NLS-1$
addExcludes(sb);
qspath.setCommand(sb.toString());
// See if the end of the path is a thread
if (!isThreadRelated && result instanceof IResultTree && result instanceof ISelectionProvider)
{
IResultTree tree = (IResultTree)result;
ISelectionProvider sel = (ISelectionProvider)result;
for (Object row : tree.getElements())
{
int r[] = findEndTree(tree, sel, row, describedObject.getObjectId(), -1);
if (r != null)
{
if (isThread(r[0]))
{
isThreadRelated = true;
int accObjId = (r[2] >= 0 && isStackFrame(r[1]) && isStackFrameLocal(r[1], r[2])) ? r[2] : r[1];
AccumulationPoint ap = accObjId >= 0 ? new AccumulationPoint(snapshot.getObject(accObjId)) : null;
IObject suspectObject = snapshot.getObject(r[0]);
SuspectRecord suspect2 = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), ap);
objectsForTroubleTicketInfo.add(suspect2.getSuspect());
overview.append("<p>"); //$NON-NLS-1$
if (ap != null)
{
overview.append(MessageUtil.format(Messages.LeakHunterQuery_ThreadLocalVariable,
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()),
HTMLUtils.escapeText(ap.getObject().getDisplayName()),
HTMLUtils.escapeText(describedObject.getDisplayName())));
}
else
{
overview.append(MessageUtil.format(Messages.LeakHunterQuery_ThreadShortestPath,
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()),
HTMLUtils.escapeText(describedObject.getDisplayName())));
}
overview.append(" "); //$NON-NLS-1$
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
overview.append("</p>"); //$NON-NLS-1$
threadDetails = extractThreadData(suspect2, keywords, objectsForTroubleTicketInfo, overview, overviewResult, monitor.nextMonitor());
threadObj = suspect2.getSuspect();
break;
}
}
}
}
}
catch (Exception e)
{
throw new SnapshotException(Messages.LeakHunterQuery_ErrorShortestPaths, e);
}
/* append keywords */
appendKeywords(keywords, overview);
// add CSN components data
appendTroubleTicketInformation(objectsForTroubleTicketInfo, overview, monitor.nextMonitor());
/*
* Prepare the composite result from the different pieces
*/
CompositeResult composite = new CompositeResult();
overviewResult.setText(overview.toString());
composite.addResult(Messages.LeakHunterQuery_Description, overviewResult);
// add the result of a path to the accumulation point
composite.addResult(qspath);
// show the acc. point in the dominator tree
IResult objectInDominatorTree = showInDominatorTree(describedObject.getObjectId());
QuerySpec qs = new QuerySpec(Messages.LeakHunterQuery_AccumulatedObjects, objectInDominatorTree);
qs.setCommand("show_dominator_tree 0x" + Long.toHexString(describedObject.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs);
// add histogram of dominated.
IResult histogramOfDominated = getHistogramOfDominated(describedObject.getObjectId(), monitor.nextMonitor());
if (histogramOfDominated != null)
{
qs = new QuerySpec(Messages.LeakHunterQuery_AccumulatedObjectsByClass, histogramOfDominated);
qs.setCommand("show_dominator_tree 0x" + Long.toHexString(describedObject.getObjectAddress()) + " -groupby BY_CLASS"); //$NON-NLS-1$//$NON-NLS-2$
composite.addResult(qs);
IResult result = SnapshotQuery.lookup("show_retained_set", snapshot) //$NON-NLS-1$
.setArgument("objects", describedObject) //$NON-NLS-1$
.execute(monitor.nextMonitor());
qs = new QuerySpec(Messages.LeakHunterQuery_AllAccumulatedObjectsByClass, result);
qs.setCommand("show_retained_set 0x" + Long.toHexString(describedObject.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs);
}
if (threadDetails != null)
{
qs = new QuerySpec(Messages.LeakHunterQuery_ThreadDetails, threadDetails);
qs.setCommand("thread_details 0x" + Long.toHexString(threadObj.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs);
}
listener.done();
return composite;
}
private void addCommand(QuerySpec spec, String command, int suspects[])
{
if (suspects.length > 0)
{
if (suspects.length <= 30)
{
try
{
StringBuilder sb = new StringBuilder(command);
for (int i : suspects)
{
sb.append(" 0x").append(Long.toHexString(snapshot.mapIdToAddress(i))); //$NON-NLS-1$
}
spec.setCommand(sb.toString());
return;
}
catch (SnapshotException e)
{} // Ignore if problem
}
// Perhaps they are all the instances of a class
try
{
IClass cls = snapshot.getClassOf(suspects[0]);
if (cls.getNumberOfObjects() == suspects.length)
{
//
int a[] = cls.getObjectIds();
int b[] = suspects.clone();
Arrays.sort(a);
Arrays.sort(b);
if (Arrays.equals(a, b))
{
Collection<IClass> cl1 = snapshot.getClassesByName(cls.getName(), false);
if (cl1 != null && cl1.size() == 1)
{
StringBuilder sb = new StringBuilder(command);
sb.append(' ');
sb.append(cls.getName());
spec.setCommand(sb.toString());
return;
}
}
}
// See if in dominator tree
}
catch (SnapshotException e)
{}
}
}
private CompositeResult getLeakDescriptionGroupOfObjects(SuspectRecordGroupOfObjects suspect, IProgressListener listener)
throws SnapshotException
{
int subq = 4;
if (suspect.getAccumulationPoint() != null)
subq += 3;
int ticks[] = new int[subq];
Arrays.fill(ticks, 100);
SimpleMonitor monitor = new SimpleMonitor(Messages.LeakHunterQuery_DescriptionGroupObjects, listener, ticks);
StringBuilder builder = new StringBuilder(256);
Set<String> keywords = new LinkedHashSet<String>();
List<IObject> objectsForTroubleTicketInfo = new ArrayList<IObject>(2);
/* get leak suspect info */
String className = ((IClass) suspect.getSuspect()).getName();
keywords.add(className);
IClassLoader classloader = (IClassLoader) snapshot
.getObject(((IClass) suspect.getSuspect()).getClassLoaderId());
objectsForTroubleTicketInfo.add(suspect.getSuspect());
String classloaderName = getClassLoaderName(classloader, keywords);
String numberOfInstances = numberFormatter.format(suspect.getSuspectInstances().length);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_InstancesOccupy, numberOfInstances, HTMLUtils.escapeText(className),
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
int[] suspectInstances = suspect.getSuspectInstances();
List<IObject> bigSuspectInstances = new ArrayList<IObject>();
for (int j = 0; j < suspectInstances.length; j++)
{
IObject inst = snapshot.getObject(suspectInstances[j]);
if (inst.getRetainedHeapSize() < (totalHeap / 100))
break;
bigSuspectInstances.add(inst);
}
if (bigSuspectInstances.size() > 0)
{
builder.append("<p>").append(Messages.LeakHunterQuery_BiggestInstances); //$NON-NLS-1$
builder.append("</p>"); //$NON-NLS-1$
builder.append("<ul title=\"").append(escapeHTMLAttribute(Messages.LeakHunterQuery_BiggestInstances)).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$
for (IObject inst : bigSuspectInstances)
{
builder.append("<li>").append(HTMLUtils.escapeText(inst.getDisplayName())); //$NON-NLS-1$
builder.append("&nbsp;-&nbsp;") //$NON-NLS-1$
.append(
MessageUtil.format(Messages.LeakHunterQuery_Msg_Bytes,
formatRetainedHeap(inst.getRetainedHeapSize(),
totalHeap)));
builder.append("</li>"); //$NON-NLS-1$
}
builder.append("</ul>"); //$NON-NLS-1$
}
/* get accumulation point info */
if (suspect.getAccumulationPoint() != null)
{
builder.append("<p>"); //$NON-NLS-1$
int accumulationPointId = suspect.getAccumulationPoint().getObject().getObjectId();
if (snapshot.isClassLoader(accumulationPointId))
{
objectsForTroubleTicketInfo.add((IClassLoader) suspect.getAccumulationPoint().getObject());
classloaderName = getClassLoaderName(suspect.getAccumulationPoint().getObject(), keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromClassLoader,
classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else if (snapshot.isClass(accumulationPointId))
{
className = ((IClass) suspect.getAccumulationPoint().getObject()).getName();
keywords.add(className);
IClassLoader accPointClassloader = (IClassLoader) snapshot.getObject(((IClass) suspect
.getAccumulationPoint().getObject()).getClassLoaderId());
// involvedClassLoaders.add(accPointClassloader);
objectsForTroubleTicketInfo.add(suspect.getAccumulationPoint().getObject());
classloaderName = getClassLoaderName(accPointClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromClass, HTMLUtils.escapeText(className),
classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else
{
className = suspect.getAccumulationPoint().getObject().getClazz().getName();
keywords.add(className);
IClassLoader accPointClassloader = (IClassLoader) snapshot.getObject(suspect.getAccumulationPoint()
.getObject().getClazz().getClassLoaderId());
// involvedClassLoaders.add(accPointClassloader);
objectsForTroubleTicketInfo.add(suspect.getAccumulationPoint().getObject());
classloaderName = getClassLoaderName(accPointClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromInstance, HTMLUtils.escapeText(className),
classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
boolean isThreadRelated = isThread(suspect.getAccumulationPoint().getObject().getObjectId());
/*
* if the class name matches the skip pattern, try to find the first
* referrer which does not match the pattern
*/
if (skipPattern.matcher(className).matches() && !isThreadRelated)
{
int referrerId = findReferrer(accumulationPointId);
if (referrerId != -1)
{
IObject referrer = snapshot.getObject(referrerId);
IObject referrerClassloader = null;
if (snapshot.isClassLoader(referrerId))
{
referrerClassloader = referrer;
objectsForTroubleTicketInfo.add(referrerClassloader);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedBy,
referrerClassloaderName));
}
else if (snapshot.isClass(referrerId))
{
className = ((IClass)referrer).getName();
keywords.add(className);
referrerClassloader = snapshot.getObject(((IClass) referrer).getClassLoaderId());
// involvedClassloaders.add(referrerClassloader);
objectsForTroubleTicketInfo.add(referrer);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByClass, HTMLUtils.escapeText(className),
referrerClassloaderName));
}
else
{
SuspectRecord suspect2 = null;
if (isThread(referrerId))
{
isThreadRelated = true;
int suspectId = referrerId;
IObject suspectObject = snapshot.getObject(suspectId);
suspect2 = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), suspect.getAccumulationPoint());
}
className = referrer.getClazz().getName();
keywords.add(className);
referrerClassloader = snapshot.getObject(referrer.getClazz().getClassLoaderId());
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(referrer);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByInstance, HTMLUtils.escapeText(referrer
.getDisplayName()), referrerClassloaderName));
if (isThreadRelated)
{
builder.append("<p>"); //$NON-NLS-1$
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
builder.append("</p>"); //$NON-NLS-1$
}
}
}
}
}
builder.append("</p>"); //$NON-NLS-1$
}
ThreadInfoQuery.Result threadDetails = null;
IObject threadObj = null;
/*
* Prepare the composite result from the different pieces
*/
CompositeResult composite = new CompositeResult();
TextResult overviewResult = new TextResult(); // used to create links
// Empty result, will be filled in later
composite.addResult(Messages.LeakHunterQuery_Description, overviewResult);
/*
* Show more details about the big instances
*/
if (bigSuspectInstances.size() > 0)
{
PieFactory pie = new PieFactory(snapshot, totalHeap);
int big[] = new int[bigSuspectInstances.size()];
long totalBig = 0;
long usedBig = 0;
for (int i = 0; i < bigSuspectInstances.size(); ++i)
{
IObject iObject = bigSuspectInstances.get(i);
big[i] = iObject.getObjectId();
pie.addSlice(big[i], iObject.getDisplayName(), iObject.getUsedHeapSize(), iObject.getRetainedHeapSize());
totalBig += iObject.getRetainedHeapSize();
usedBig += iObject.getUsedHeapSize();
}
long totalSuspects = 0;
long usedSuspects = 0;
for (int s : suspectInstances)
{
IObject inst = snapshot.getObject(s);
totalSuspects += inst.getRetainedHeapSize();
usedSuspects += inst.getUsedHeapSize();
}
pie.addSlice(-1, Messages.LeakHunterQuery_OtherSuspectInstances, usedSuspects - usedBig, totalSuspects - totalBig);
QuerySpec specPie = new QuerySpec(Messages.LeakHunterQuery_BiggestInstancesOverview, pie.build());
specPie.set(Params.Html.COLLAPSED, Boolean.TRUE.toString());
composite.addResult(specPie);
QuerySpec spec = new QuerySpec(Messages.LeakHunterQuery_BiggestInstancesHeading,
new ObjectListResult.Outbound(snapshot, big));
try
{
StringBuilder sb = new StringBuilder("list_objects"); //$NON-NLS-1$
for (int i : big)
{
sb.append(" 0x").append(Long.toHexString(snapshot.mapIdToAddress(i))); //$NON-NLS-1$
}
spec.setCommand(sb.toString());
}
catch (SnapshotException e)
{} // Ignore if problem
spec.set(Params.Html.COLLAPSED, Boolean.TRUE.toString());
composite.addResult(spec);
}
// add histogram of suspects and show objects they retain
if (true)
{
IObject io = suspect.getSuspect();
String oql = null;
if (io instanceof IClass)
{
String cn = OQLclassName((IClass)io);
oql = "SELECT * FROM " + cn + " s WHERE dominatorof(s) = null"; //$NON-NLS-1$ //$NON-NLS-2$
}
RefinedResultBuilder rbuilder = SnapshotQuery.lookup("histogram", snapshot) //$NON-NLS-1$
.setArgument("objects", suspectInstances) //$NON-NLS-1$
.refine(monitor.nextMonitor());
rbuilder.setInlineRetainedSizeCalculation(true);
rbuilder.addDefaultContextDerivedColumn(RetainedSizeDerivedData.PRECISE);
IResult result = rbuilder.build();
QuerySpec qs = new QuerySpec(Messages.LeakHunterQuery_SuspectObjectsByClass, result);
addCommand(qs, "histogram", suspectInstances); //$NON-NLS-1$
if (qs.getCommand() == null)
{
qs.setCommand("histogram " + oql +";"); //$NON-NLS-1$ //$NON-NLS-2$
}
qs.set(Params.Html.COLLAPSED, Boolean.TRUE.toString());
composite.addResult(qs);
rbuilder = SnapshotQuery.lookup("show_retained_set", snapshot) //$NON-NLS-1$
.setArgument("objects", suspectInstances) //$NON-NLS-1$
.refine(monitor.nextMonitor());
rbuilder.setInlineRetainedSizeCalculation(true);
rbuilder.addDefaultContextDerivedColumn(RetainedSizeDerivedData.APPROXIMATE);
result = rbuilder.build();
qs = new QuerySpec(Messages.LeakHunterQuery_AllObjectsByClassRetained, result);
addCommand(qs, "show_retained_set", suspectInstances); //$NON-NLS-1$
if (qs.getCommand() == null)
{
qs.setCommand("show_retained_set " + oql +';'); //$NON-NLS-1$
}
qs.set(Params.Html.COLLAPSED, Boolean.TRUE.toString());
composite.addResult(qs);
}
AccumulationPoint accPoint = suspect.getAccumulationPoint();
if (accPoint != null)
{
QuerySpec qs = new QuerySpec(Messages.LeakHunterQuery_CommonPath, //
MultiplePath2GCRootsQuery.create(snapshot, suspect.getPathsComputer(), suspect.getCommonPath(), monitor.nextMonitor()));
int paths[];
if (suspect.suspectInstances.length <= 25)
{
// Use all the objects
paths = suspect.suspectInstances;
}
else if (suspect.getCommonPath().length > 0)
{
// Just use the last of the common path
paths = new int[] { suspect.getCommonPath()[suspect.getCommonPath().length - 1] };
}
else
{
paths = new int[0];
}
if (paths.length > 0)
{
StringBuilder sb = new StringBuilder("merge_shortest_paths"); //$NON-NLS-1$
for (int objId : paths)
{
long addr = snapshot.mapIdToAddress(objId);
sb.append(" 0x").append(Long.toHexString(addr)); //$NON-NLS-1$
}
addExcludes(sb);
qs.setCommand(sb.toString());
}
composite.addResult(qs);
int path[] = suspect.getCommonPath();
if (path.length > 0)
{
if (isThread(path[0]))
{
AccumulationPoint ap;
if (path.length > 2 && isStackFrame(path[1]) && isStackFrameLocal(path[1], path[2]))
{
ap = new AccumulationPoint(snapshot.getObject(path[2]));
}
else if (path.length > 1)
{
ap = new AccumulationPoint(snapshot.getObject(path[1]));
}
else
{
ap = null;
}
IObject suspectObject = snapshot.getObject(path[0]);
SuspectRecord suspect2 = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), ap);
objectsForTroubleTicketInfo.add(suspect2.getSuspect());
// Description
builder.append("<p>"); //$NON-NLS-1$
if (ap != null)
{
builder.append(MessageUtil.format(Messages.LeakHunterQuery_ThreadLocalVariable,
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()),
HTMLUtils.escapeText(ap.getObject().getDisplayName()),
HTMLUtils.escapeText(accPoint.getObject().getDisplayName())));
}
else
{
builder.append(MessageUtil.format(Messages.LeakHunterQuery_ThreadShortestPath,
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()),
HTMLUtils.escapeText(accPoint.getObject().getDisplayName())));
}
builder.append(" "); //$NON-NLS-1$
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
builder.append("</p>"); //$NON-NLS-1$
threadDetails = extractThreadData(suspect2, keywords, objectsForTroubleTicketInfo, builder, overviewResult, monitor.nextMonitor());
threadObj = suspect2.getSuspect();
// add keywords to builder later
// add CSN components data to builder later
}
}
// show the acc. point in the dominator tree
IObject describedObject = accPoint.getObject();
IResult objectInDominatorTree = showInDominatorTree(describedObject.getObjectId());
QuerySpec qs2 = new QuerySpec(Messages.LeakHunterQuery_AccumulatedObjects, objectInDominatorTree);
qs2.setCommand("show_dominator_tree 0x" + Long.toHexString(describedObject.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs2);
// add histogram of dominated.
IResult histogramOfDominated = getHistogramOfDominated(describedObject.getObjectId(), monitor.nextMonitor());
if (histogramOfDominated != null)
{
qs = new QuerySpec(Messages.LeakHunterQuery_AccumulatedObjectsByClass, histogramOfDominated);
qs.setCommand("show_dominator_tree 0x" + Long.toHexString(describedObject.getObjectAddress()) + " -groupby BY_CLASS"); //$NON-NLS-1$//$NON-NLS-2$
composite.addResult(qs);
IResult result = SnapshotQuery.lookup("show_retained_set", snapshot) //$NON-NLS-1$
.setArgument("objects", describedObject) //$NON-NLS-1$
.execute(monitor.nextMonitor());
qs = new QuerySpec(Messages.LeakHunterQuery_AllAccumulatedObjectsByClass, result);
qs.setCommand("show_retained_set 0x" + Long.toHexString(describedObject.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs);
}
}
else
{
IResult result = findReferencePattern(suspect, monitor.nextMonitor());
if (result != null)
{
String msg = (suspect.getSuspectInstances().length > max_paths) ?
MessageUtil.format(Messages.LeakHunterQuery_ReferencePatternFor, max_paths) :
Messages.LeakHunterQuery_ReferencePattern;
QuerySpec qs = new QuerySpec(msg, result);
IObject io = suspect.getSuspect();
if (io instanceof IClass)
{
// Suspect has paths computer with supplied excludes
String cn = OQLclassName((IClass)io);
StringBuilder sb = new StringBuilder("merge_shortest_paths SELECT * FROM "); //$NON-NLS-1$
sb.append(cn).append(" s WHERE dominatorof(s) = null; -groupby FROM_GC_ROOTS_BY_CLASS"); //$NON-NLS-1$
addExcludes(sb);
qs.setCommand(sb.toString());
}
composite.addResult(qs);
}
}
// Postpone building overview until now
// append keywords
appendKeywords(keywords, builder);
// add CSN components data
appendTroubleTicketInformation(objectsForTroubleTicketInfo, builder, monitor.nextMonitor());
overviewResult.setText(builder.toString());
if (threadDetails != null)
{
QuerySpec qs = new QuerySpec(Messages.LeakHunterQuery_ThreadDetails, threadDetails);
qs.setCommand("thread_details 0x" + Long.toHexString(threadObj.getObjectAddress())); //$NON-NLS-1$
composite.addResult(qs);
}
listener.done();
return composite;
}
void addExcludes(StringBuilder sb)
{
boolean hasDefault = true;
if (excludes != null && !excludes.isEmpty() || hasDefault)
{
sb.append(" -excludes"); //$NON-NLS-1$
if (excludes != null && !excludes.isEmpty())
{
for (String ex : excludes)
{
sb.append(' ').append(Converters.convertAndEscape(String.class, ex));
}
}
else
{
sb.append(' ');
}
sb.append(';');
}
}
private String escapeHTMLAttribute(String msg)
{
return HTMLUtils.escapeText(msg).replaceAll("\"", "&quote;").replaceAll("'", "&apos;"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
/**
* Find the end of the path to GC roots.
* Relies on end being selected.
* @param tree the tree
* @param sel the selection provider (the tree)
* @param row the current row
* @param prev previous object in tree
* @param prev2 the object before the previous object in tree
* @return array [root object, previous object traversed, second previous object traversed] or
* null if no selected object at root
*/
private int[] findEndTree(IResultTree tree, ISelectionProvider sel, Object row, int prev, int prev2)
{
if (sel.isSelected(row))
{
IContextObject x = tree.getContext(row);
if (x != null && x.getObjectId() >= 0)
return new int[] {x.getObjectId(), prev, prev2};
}
if (sel.isExpanded(row))
{
// Recurse
IContextObject x = tree.getContext(row);
if (x != null)
{
prev2 = prev;
prev = x.getObjectId();
}
List<?> children = tree.getChildren(row);
if (children != null)
{
for (Object r2 : children)
{
int ret[] = findEndTree(tree, sel, r2, prev, prev2);
if (ret != null)
return ret;
}
}
}
return null;
}
private String OQLclassName(IClass ic)
{
String className = ic.getName();
// Check class name is sensible and will parse (not a full Java identifier test)
// and is unique
try
{
Collection<IClass> classesByName = ic.getSnapshot().getClassesByName(className, false);
if (className.matches("\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}.]*") && classesByName != null && classesByName.size() == 1) //$NON-NLS-1$
{
return className;
}
}
catch (SnapshotException e)
{
}
return "0x" + Long.toHexString(ic.getObjectAddress()); //$NON-NLS-1$
}
private String formatRetainedHeap(long retained, long totalHeap)
{
return bytesFormatter.format(retained) + " (" //$NON-NLS-1$
+ percentFormatter.format((double) retained / (double) totalHeap) + ")"; //$NON-NLS-1$
}
private Map<String, String> getTroubleTicketMapping(ITroubleTicketResolver resolver, List<IObject> classloaders, IProgressListener listener)
throws SnapshotException
{
Map<String, String> mapping = new HashMap<String, String>();
for (IObject suspect : classloaders)
{
String ticket = null;
String key = null;
if (suspect instanceof IClassLoader)
{
ticket = resolver.resolveByClassLoader((IClassLoader) suspect, listener);
if (ticket != null && !"".equals(ticket.trim())) //$NON-NLS-1$
{
key = suspect.getClassSpecificName();
if (key == null) key = suspect.getTechnicalName();
}
}
else
{
IClass clazz = (suspect instanceof IClass) ? (IClass) suspect : suspect.getClazz();
ticket = resolver.resolveByClass(clazz, listener);
key = clazz.getName();
}
if (ticket != null)
{
String old = mapping.put(ticket, key);
if (old != null) mapping.put(ticket, key + ", " + old); //$NON-NLS-1$
}
}
return mapping;
}
private String getName(IObject object)
{
String name = object.getClassSpecificName();
if (name == null)
{
name = object.getTechnicalName();
}
return name;
}
/**
* Get the name of the class loader.
* @param classloader
* @param keywords
* @return The name with HTML escapes already applied.
*/
private String getClassLoaderName(IObject classloader, Set<String> keywords)
{
if (classloader.getObjectAddress() == 0)
{
return SYSTEM_CLASSLOADER;
}
else
{
String classloaderName = getName(classloader);
if (keywords != null)
{
// Do not want the address in the keyword, so do not use getTechnicalName()
String keywordName = classloader.getClassSpecificName();
if (keywordName == null)
keywordName = classloader.getClazz().getName();
keywords.add(keywordName);
}
return HTMLUtils.escapeText(classloaderName);
}
}
private IResult showInDominatorTree(int objectId) throws SnapshotException
{
// show the acc. point in the dominator tree
Stack<Integer> tmp = new Stack<Integer>();
int e = objectId;
while (e != -1)
{
tmp.push(e);
e = snapshot.getImmediateDominatorId(e);
}
ObjectTreeFactory.TreePathBuilder treeBuilder = new ObjectTreeFactory.TreePathBuilder(snapshot
.getSnapshotInfo().getUsedHeapSize());
treeBuilder.setIsOutgoing();
treeBuilder.addBranch(tmp.pop());
while (tmp.size() > 0)
{
e = tmp.pop();
treeBuilder.addChild(e, e == objectId);
}
int[] dominatedByAccPoint = snapshot.getImmediateDominatedIds(objectId);
for (int i = 0; i < 20 && i < dominatedByAccPoint.length; i++)
{
treeBuilder.addSibling(dominatedByAccPoint[i], false);
}
return treeBuilder.build(snapshot);
}
private IResult getHistogramOfDominated(int objectId, IProgressListener listener) throws SnapshotException
{
int[] dominatedByAccPoint = snapshot.getImmediateDominatedIds(objectId);
Histogram h = snapshot.getHistogram(dominatedByAccPoint, listener);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
ClassHistogramRecord[] records = h.getClassHistogramRecords().toArray(new ClassHistogramRecord[0]);
for (ClassHistogramRecord record : records)
{
record.setRetainedHeapSize(snapshot.getMinRetainedSize(record.getObjectIds(), listener));
}
Arrays.sort(records, Histogram.reverseComparator(Histogram.COMPARATOR_FOR_RETAINEDHEAPSIZE));
ArrayList<ClassHistogramRecord> suspects = new ArrayList<ClassHistogramRecord>();
int limit = 0;
for (ClassHistogramRecord record : records)
{
if (limit >= 20)
break;
suspects.add(record);
limit++;
}
ListResult result = new ListResult(ClassHistogramRecord.class, suspects, "label", //$NON-NLS-1$
"numberOfObjects", "usedHeapSize", "retainedHeapSize") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
{
@Override
public URL getIcon(Object row)
{
return Icons.forObject(snapshot, ((ClassHistogramRecord) row).getClassId());
}
@Override
public IContextObject getContext(final Object row)
{
return new IContextObjectSet()
{
public int getObjectId()
{
return ((ClassHistogramRecord) row).getClassId();
}
public int[] getObjectIds()
{
return ((ClassHistogramRecord) row).getObjectIds();
}
public String getOQL()
{
int clsid = ((ClassHistogramRecord) row).getClassId();
return "SELECT OBJECTS s FROM OBJECTS (dominators(" + objectId + ")) s where classof(s).@objectId = " + clsid; //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
};
return result;
}
private void appendKeywords(Set<String> keywords, StringBuilder builder)
{
String title = Messages.LeakHunterQuery_Keywords;
builder.append("<p><strong>").append(title).append("</strong>"); //$NON-NLS-1$ //$NON-NLS-2$
builder.append("</p>"); //$NON-NLS-1$
builder.append("<ul style=\"list-style-type:none;\" title=\"").append(escapeHTMLAttribute(title)).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$
for (String s : keywords)
builder.append("<li>").append(HTMLUtils.escapeText(s)).append("</li>"); //$NON-NLS-1$ //$NON-NLS-2$
builder.append("</ul>"); //$NON-NLS-1$
}
private void appendTroubleTicketInformation(List<IObject> classloaders, StringBuilder builder, IProgressListener listener)
throws SnapshotException
{
for (ITroubleTicketResolver resolver : TroubleTicketResolverRegistry.instance().delegates())
{
Map<String, String> mapping = getTroubleTicketMapping(resolver, classloaders, listener);
if (!mapping.isEmpty())
{
String title = resolver.getTicketSystem();
builder.append("<p><strong>").append(HTMLUtils.escapeText(title)).append("</strong>"); //$NON-NLS-1$ //$NON-NLS-2$
builder.append("</p>"); //$NON-NLS-1$
builder.append("<ul style=\"list-style-type:none;\" title=\"").append(escapeHTMLAttribute(title)).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$
for (Map.Entry<String, String> entry : mapping.entrySet())
{
builder.append("<li>").append( //$NON-NLS-1$
MessageUtil.format(Messages.LeakHunterQuery_TicketForSuspect, HTMLUtils.escapeText(entry.getKey()), HTMLUtils.escapeText(entry
.getValue()))).append("</li>"); //$NON-NLS-1$
}
builder.append("</ul>"); //$NON-NLS-1$
}
}
}
private ThreadInfoQuery.Result extractThreadData(SuspectRecord suspect, Set<String> keywords,
List<IObject> involvedClassloaders, StringBuilder builder, TextResult textResult, IProgressListener listener)
{
final int threadId = suspect.getSuspect().getObjectId();
ThreadInfoQuery.Result threadDetails = null;
try
{
threadDetails = (ThreadInfoQuery.Result) SnapshotQuery.lookup("thread_details", snapshot) //$NON-NLS-1$
.setArgument("threadIds", threadId) //$NON-NLS-1$
.execute(listener);
// append overview & keywords
IThreadInfo threadInfo = threadDetails.getThreads().get(0);
keywords.addAll(threadInfo.getKeywords());
CompositeResult requestInfos = threadInfo.getRequests();
if (requestInfos != null && !requestInfos.isEmpty())
{
builder.append("<p>"); //$NON-NLS-1$
builder.append("</p>"); //$NON-NLS-1$
builder.append("<ul style=\"list-style-type:none;\">"); //$NON-NLS-1$
for (CompositeResult.Entry requestInfo : requestInfos.getResultEntries())
builder.append("<li>").append(HTMLUtils.escapeText(requestInfo.getName())).append(" ").append( //$NON-NLS-1$ //$NON-NLS-2$
textResult.linkTo(Messages.LeakHunterQuery_RequestDetails,
requestInfo.getResult())).append("</li>"); //$NON-NLS-1$
builder.append("</ul>"); //$NON-NLS-1$
}
// Add stacktrace information if available
// TODO may be the stack result should be moved to IThreadInfo
IThreadStack stack = snapshot.getThreadStack(threadId);
if (stack != null)
{
// Find the local variables involved in the path to the accumulation point
final SetInt locals = new SetInt();
IObject acc = suspect.getAccumulationPoint() != null ? suspect.getAccumulationPoint().getObject()
: null;
if (acc != null)
{
IResultTree tree = (IResultTree) SnapshotQuery.lookup("merge_shortest_paths", snapshot) //$NON-NLS-1$
.setArgument("objects", acc) //$NON-NLS-1$
.setArgument("excludes", excludes) //$NON-NLS-1$
.execute(listener);
for (Object row : tree.getElements())
{
IContextObject co = tree.getContext(row);
if (co.getObjectId() == threadId)
{
for (Object row2 : tree.getChildren(row))
{
IContextObject co2 = tree.getContext(row2);
int o2 = co2.getObjectId();
if (o2 >= 0)
{
/*
* Stack frames as objects?
* If so, then find the variables in those frames.
*/
if (isStackFrame(o2))
{
// So find the actual variable in the frame
for (Object row3 : tree.getChildren(row2))
{
IContextObject co3 = tree.getContext(row3);
int o3 = co3.getObjectId();
if (o3 >= 0)
locals.add(o3);
}
// Allow for accumulation point being the frame itself
if (o2 == acc.getObjectId())
locals.add(o2);
}
else
{
locals.add(o2);
}
}
}
}
}
}
StringBuilder stackBuilder = new StringBuilder();
IObject threadObject = snapshot.getObject(threadId);
String threadName = threadObject.getClassSpecificName();
if (threadName == null)
threadName = threadObject.getTechnicalName();
stackBuilder.append(threadName).append("\r\n"); //$NON-NLS-1$
// Have some locals already been identifier as significant?
boolean foundLocals = !locals.isEmpty();
// Is one variable a significant part of the suspect
long significantLocal = (long)(0.1 * suspect.getSuspectRetained());
// Are several variables from a frame that significant together
long significantFrame = (long)(0.25 * suspect.getSuspectRetained());
List<Map<String,SetInt>> involvedFrames = new ArrayList<>();
for (IStackFrame frame : stack.getStackFrames())
{
boolean involved = false;
SetInt frameLocals = new SetInt();
for (int l : frame.getLocalObjectsIds())
{
if (!foundLocals)
{
/*
* We didn't find a local on the accumulation point path, so
* just look for big locals.
*/
if (snapshot.getRetainedHeapSize(l) > significantLocal)
{
int dom = snapshot.getImmediateDominatorId(l);
// Also allow for stack frames as objects
if (dom == threadId || dom >= 0 && snapshot.getImmediateDominatorId(dom) == threadId)
{
locals.add(l);
frameLocals.add(l);
involved = true;
}
}
}
else if (locals.contains(l))
{
frameLocals.add(l);
involved = true;
}
}
if (!involved && !foundLocals)
{
/*
* Check whether several variables in this frame
* dominated by the thread together retain a lot.
*/
SetInt dominated = new SetInt();
for (int l : frame.getLocalObjectsIds())
{
int dom = snapshot.getImmediateDominatorId(l);
// Also allow for stack frames as objects
if (dom == threadId || dom >= 0 && snapshot.getImmediateDominatorId(dom) == threadId)
{
dominated.add(l);
}
}
int doms[] = dominated.toArray();
long domsize = snapshot.getMinRetainedSize(doms, listener);
if (domsize > significantFrame)
{
int doms1[] = doms;
/*
* Eliminate variables until we lose no more than 20% of the size.
*/
long significantFrame1 = (long)(0.8 * domsize);
do
{
doms = doms1;
long dombest = 0;
doms1 = null;
// Try removing each local
for (int i = 0; i < doms.length; ++i)
{
// Remove a local
int doms2[] = new int[doms.length - 1];
for (int j = 0; j < doms2.length; ++j)
{
doms2[j] = doms[j >= i ? j + 1 : j];
}
// See the retained size now
long domsize1 = snapshot.getMinRetainedSize(doms2, listener);
if (domsize1 > dombest && domsize1 > significantFrame1)
{
doms1 = doms2;
dombest = domsize1;
}
}
} while (doms1 != null);
for (int l : doms)
{
locals.add(l);
frameLocals.add(l);
}
involved = true;
}
}
stackBuilder.append(" ").append(frame.getText()).append("\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
if (involved)
{
/*
* Store details about the involved frame
* so we can filter out JDK frames later
*/
String frameText = frame.getText();
String p[] = frameText.split("\\s+", 2); //$NON-NLS-1$
Map <String,SetInt>m = new HashMap<>();
m.put(p.length > 1 ? p[1] : "", frameLocals); //$NON-NLS-1$
involvedFrames.add(m);
}
}
if (involvedFrames.size() > 0)
{
// Find the top skipped frames e.g. JDK
int skipped = 0;
for (skipped = 0; skipped < involvedFrames.size(); ++skipped)
{
String frameName = involvedFrames.get(skipped).keySet().iterator().next();
if (!skipPattern.matcher(frameName).matches())
break;
}
// Remove duplicated variables from skipped frames
for (int i = skipped; i < involvedFrames.size(); ++i)
{
SetInt vars = involvedFrames.get(i).values().iterator().next();
for (int j = 0; j < skipped; ++j)
{
SetInt vars2 = involvedFrames.get(j).values().iterator().next();
for (int v : vars.toArray())
{
vars2.remove(v);
}
}
}
builder.append("<p>").append(Messages.LeakHunterQuery_SignificantStackFrames).append("</p><ul>"); //$NON-NLS-1$//$NON-NLS-2$
for (int i = 0; i < involvedFrames.size(); ++i)
{
SetInt frameLocals = involvedFrames.get(i).values().iterator().next();
if (frameLocals.isEmpty())
continue;
String frameName = involvedFrames.get(i).keySet().iterator().next();
String p[] = frameName.split("\\s+", 2); //$NON-NLS-1$
keywords.add(p[0]);
// Identify the class for the frame and add it
String className = p[0];
int firstParen = className.indexOf('(');
int lastDot = className.lastIndexOf('.', firstParen >= 0 ? firstParen : Integer.MAX_VALUE);
if (lastDot > 0)
{
className = className.substring(0, lastDot);
Collection<IClass> clss = snapshot.getClassesByName(className, false);
if (clss != null && clss.size() == 1)
{
involvedClassloaders.add(clss.iterator().next());
}
}
// Extract the source file
if (p.length > 1)
{
// (MyClass.java(Compiled Code))
// Remove parentheses and (Compiled Code) or (Native Method)
int end = p[1].indexOf('(', 1);
if (end < 0)
end = p[1].length() - 1;
keywords.add(p[1].substring(1, end));
}
// Add the frame and the interesting locals to the list
builder.append("<li>"); //$NON-NLS-1$
builder.append(HTMLUtils.escapeText(p[0]));
if (p.length > 1)
{
builder.append(' ').append(HTMLUtils.escapeText(p[1]));
}
builder.append("<ul>"); //$NON-NLS-1$
for (int v : frameLocals.toArray())
{
IObject obj = snapshot.getObject(v);
builder.append("<li>").append(MessageUtil.format(Messages.LeakHunterQuery_Retains, //$NON-NLS-1$
HTMLUtils.escapeText(obj.getDisplayName()),
formatRetainedHeap(snapshot.getRetainedHeapSize(v), totalHeap)))
.append("</li>"); // $NON-NLS-1$ //$NON-NLS-1$
}
builder.append("</ul>"); //$NON-NLS-1$
builder.append("</li>"); //$NON-NLS-1$
}
builder.append("</ul>"); //$NON-NLS-1$
}
QuerySpec stackResult = new QuerySpec(Messages.LeakHunterQuery_ThreadStack, new TextResult(stackBuilder
.toString()));
stackResult.setCommand("thread_details 0x" + Long.toHexString(threadObject.getObjectAddress())); //$NON-NLS-1$
builder.append("<p>"); //$NON-NLS-1$
builder.append(Messages.LeakHunterQuery_StackTraceAvailable).append(" ").append( //$NON-NLS-1$
textResult.linkTo(Messages.LeakHunterQuery_SeeStackstrace, stackResult)).append('.');
if (!locals.isEmpty())
{
RefinedResultBuilder rbuilder = SnapshotQuery.lookup("thread_overview", snapshot) //$NON-NLS-1$
.setArgument("objects", suspect.getSuspect()) //$NON-NLS-1$
.refine(listener);
final RefinedTree rt = (RefinedTree) rbuilder.build();
rt.setSelectionProvider(new ISelectionProvider()
{
/**
* Select the thread and the involved local variables.
*/
public boolean isSelected(Object row)
{
IContextObject co = rt.getContext(row);
if (co != null && (locals.contains(co.getObjectId()) || co.getObjectId() == threadId))
return true;
// Also select the stack frame(s) containing the locals
return isExpanded(row);
}
/**
* Expand the thread and a row if it refers to an involved
* local variable.
*/
public boolean isExpanded(Object row)
{
// Any thread should be expanded
if (rt.getElements().contains(row))
return true;
for (Object r2 : rt.getChildren(row))
{
// If row has a child which is a local
IContextObject co = rt.getContext(r2);
if (co != null && locals.contains(co.getObjectId()))
{
/*
* It needs to be a stack frame row though to
* be expanded.
*/
for (Object r3 : rt.getElements())
{
// Relies on ThreadOverviewQuery.ThreadStackFrameNode equals()
if (rt.getChildren(r3).contains(row))
return true;
}
}
}
return false;
}
});
QuerySpec threadResult = new QuerySpec(Messages.LeakHunterQuery_ThreadStackAndLocals, rt);
// Make sure the whole stack trace is expanded
List<?> lThreads = rt.getElements();
// rt always has some sort of selection provider
if (lThreads.size() >= 1 && rt.hasChildren(lThreads.get(0)))
{
ISelectionProvider sel = rt;
List<?> lFrames = rt.getChildren(lThreads.get(0));
int limit = lFrames.size();
for (Object row : lFrames)
{
// Max sure all expanded stack frames are rendered in full
if (sel.isExpanded(row))
limit = Math.max(limit, rt.getChildren(row).size());
}
threadResult.set(Params.Rendering.LIMIT, String.valueOf(limit));
}
threadResult.setCommand(
"thread_overview 0x" + Long.toHexString(suspect.getSuspect().getObjectAddress())); //$NON-NLS-1$
builder.append(" ").append( //$NON-NLS-1$
textResult.linkTo(Messages.LeakHunterQuery_SeeStackstraceVars, threadResult))
.append('.');
}
builder.append("</p>"); //$NON-NLS-1$
}
// add context class loader
int contextClassloaderId = threadInfo.getContextClassLoaderId();
if (contextClassloaderId != 0)
{
involvedClassloaders.add((IClassLoader) snapshot.getObject(contextClassloaderId));
}
}
catch (Exception e)
{
Logger.getLogger(getClass().getName()).log(Level.SEVERE,
Messages.LeakHunterQuery_ErrorRetrievingRequestDetails, e);
}
return threadDetails;
}
private IResult findReferencePattern(SuspectRecordGroupOfObjects suspect, IProgressListener listener) throws SnapshotException
{
MultiplePathsFromGCRootsClassRecord dummy = new MultiplePathsFromGCRootsClassRecord(null, -1, true, snapshot);
Object[] allPaths = suspect.getPathsComputer().getAllPaths(listener);
for (Object path : allPaths)
dummy.addPath((int[]) path);
MultiplePathsFromGCRootsClassRecord[] classRecords = dummy.nextLevel();
int numPaths = allPaths.length;
double factor = 0.8;
double threshold = numPaths * factor;
List<IClass> referencePattern = new ArrayList<IClass>();
if (classRecords.length > 0)
{
Arrays.sort(classRecords, MultiplePathsFromGCRootsClassRecord.getComparatorByNumberOfReferencedObjects());
MultiplePathsFromGCRootsClassRecord r = classRecords[0];
while (r.getCount() > threshold)
{
threshold = r.getCount() * factor;
referencePattern.add(r.getClazz());
classRecords = r.nextLevel();
if (classRecords == null || classRecords.length == 0)
break;
Arrays.sort(classRecords,
MultiplePathsFromGCRootsClassRecord.getComparatorByNumberOfReferencedObjects());
r = classRecords[0];
}
}
/*
* build the tree
*/
int expandedClasses[] = new int[referencePattern.size()];
for (int i = 0; i < referencePattern.size(); ++i)
{
expandedClasses[i] = referencePattern.get(i).getObjectId();
}
return MultiplePath2GCRootsQuery.create(snapshot, suspect.getPathsComputer(), expandedClasses, true, listener);
}
private List<CompositeResult> findCommonPathForSuspects(HashMap<Integer, List<Integer>> accPoint2ProblemNr, IProgressListener listener)
throws SnapshotException
{
List<CompositeResult> result = new ArrayList<CompositeResult>(2);
// get all accumulation point ids
int[] objectIds = new int[accPoint2ProblemNr.size()];
int j = 0;
for (Integer accPointId : accPoint2ProblemNr.keySet())
objectIds[j++] = accPointId;
// calculate the shortest paths to all accumulation points
// avoid weak paths
// Unfinalized objects from J9
// convert excludes into the required format
Map<IClass, Set<String>> excludeMap = ExcludesConverter.convert(snapshot, excludes);
IMultiplePathsFromGCRootsComputer comp = snapshot.getMultiplePathsFromGCRoots(objectIds, excludeMap);
MultiplePathsFromGCRootsRecord[] records = comp.getPathsByGCRoot(listener);
Arrays.sort(records, MultiplePathsFromGCRootsRecord.getComparatorByNumberOfReferencedObjects());
for (MultiplePathsFromGCRootsRecord rec : records)
{
if (rec.getCount() < 2)
break; // no more common paths
// build an overview for the problems with common paths
CompositeResult composite = new CompositeResult();
List<Integer> problemIds = new ArrayList<Integer>(4);
int[] referencedAccumulationPoints = rec.getReferencedObjects();
for (int accPointId : referencedAccumulationPoints)
{
for (Integer problemId : accPoint2ProblemNr.get(accPointId))
{
problemIds.add(problemId);
}
}
Collections.sort(problemIds);
StringBuilder overview = new StringBuilder(256);
StringBuilder left = new StringBuilder();
for (int k = 0; k < problemIds.size() - 1; k++)
{
if (left.length() > 0)
left.append(", "); //$NON-NLS-1$
left.append(problemIds.get(k));
}
overview.append(MessageUtil.format(
Messages.LeakHunterQuery_Msg_SuspectsRelated, left.toString(), problemIds.get(problemIds.size() - 1)));
composite.addResult(Messages.LeakHunterQuery_Overview, new TextResult(overview.toString(), true));
// END build overview
// find the path which ALL acc.points share
MultiplePathsFromGCRootsRecord parentRecord = rec;
ArrayIntBig commonPath = new ArrayIntBig();
while (parentRecord.getCount() == rec.getCount())
{
commonPath.add(parentRecord.getObjectId());
MultiplePathsFromGCRootsRecord[] children = parentRecord.nextLevel();
if (children == null || children.length == 0)
break; // reached the end
// take the child with most paths and try again
Arrays.sort(children, MultiplePathsFromGCRootsRecord.getComparatorByNumberOfReferencedObjects());
parentRecord = children[0];
}
// provide the common path as details
// Only show the paths for the objects in common
IMultiplePathsFromGCRootsComputer comp2 = snapshot.getMultiplePathsFromGCRoots(referencedAccumulationPoints, excludeMap);
QuerySpec qs = new QuerySpec(Messages.LeakHunterQuery_CommonPath, //
MultiplePath2GCRootsQuery.create(snapshot, comp2, commonPath.toArray(), listener));
StringBuilder sb = new StringBuilder("merge_shortest_paths"); //$NON-NLS-1$
for (int objId : referencedAccumulationPoints)
{
long addr = snapshot.mapIdToAddress(objId);
sb.append(" 0x").append(Long.toHexString(addr)); //$NON-NLS-1$
}
//Currently a bug in parsing multiple command line arguments so require another
//named argument after -excludes
addExcludes(sb);
sb.append(" -groupby FROM_GC_ROOTS"); //$NON-NLS-1$
qs.setCommand(sb.toString());
composite.addResult(qs);
result.add(composite);
}
return result;
}
private int findReferrer(int objectId) throws SnapshotException
{
BitField skipped = new BitField(snapshot.getSnapshotInfo().getNumberOfObjects());
Collection<IClass> classes = snapshot.getClassesByName(skipPattern, false);
for (IClass clazz : classes)
for (int instance : clazz.getObjectIds())
skipped.set(instance);
BitField visited = new BitField(snapshot.getSnapshotInfo().getNumberOfObjects());
LinkedList<int[]> fifo = new LinkedList<int[]>();
fifo.add(new int[] { objectId });
while (fifo.size() > 0)
{
int[] e = fifo.removeFirst();
for (int referrer : e)
{
if (!visited.get(referrer))
{
/*
* If it is a non-interesting object based on class name then look further.
* Threads might be named java.lang.Thread but have an interesting
* stack trace so do consider threads.
* Sometimes it isn't so clear - e.g. a java.util.concurrent.ForkJoinTask[]
* might be referred to from several ForkJoinWorkerThreads
*/
if (skipped.get(referrer) && !isThread(referrer))
{
int[] referrers = snapshot.getInboundRefererIds(referrer);
fifo.add(referrers);
visited.set(referrer);
}
else
{
return referrer;
}
}
}
}
return -1;
}
}