561460: More comparison queries
First version to find leaks between two snapshots.
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=561460
diff --git a/plugins/org.eclipse.mat.api/META-INF/reports/overview2.xml b/plugins/org.eclipse.mat.api/META-INF/reports/overview2.xml
new file mode 100644
index 0000000..be50872
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/META-INF/reports/overview2.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2008, 2020 SAP AG and IBM Corporation.
+ 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
+ Andrew Johnson - comparison queries
+ -->
+<section name="%overview2.title System Overview comparison" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://www.eclipse.org/mat/report.xsd" xsi:schemaLocation="http://www.eclipse.org/mat/report.xsd platform:/resource/org.eclipse.mat.report/schema/report.xsd">
+
+ <param key="html.collapsed" value="false" />
+ <param key="filename_suffix" value="%overview2.suffix Overview Delta" />
+
+ <query name="%overview.heap_dump_overview">
+ <param key="html.show_table_header" value="false" />
+ <command>simplecomparison -query heap_dump_overview -baseline ${baseline}</command>
+ </query>
+ <query name="%overview.system_properties">
+ <param key="html.separate_file" value="true" />
+ <param key="sort_column" value="#0=ASC" />
+ <param key="hide_column" value="#1,#2" />
+ <param key="limit" value="1000" />
+ <command>simplecomparison -query system_properties -options "-keycolumn 2" -baseline ${baseline}</command>
+ </query>
+ <query name="%overview.threads">
+ <param key="limit" value="1000" />
+ <param key="html.separate_file" value="true" />
+ <param key="html.render_details" value="false" />
+ <command>simplecomparison -query thread_overview -baseline ${baseline}</command>
+ </query>
+ <query name="%overview.class_histogram">
+ <param key="html.separate_file" value="true" />
+ <param key="sort_column" value="#6" />
+ <command>simplecomparison -query histogram -retained APPROXIMATE -options "-mode DIFF_TO_PREVIOUS" -baseline ${baseline}</command>
+ </query>
+ <!-- Expensive to calculate, so use version from LeaKHunter2
+ <query name="%overview2.dominator_tree Dominator tree">
+ <param key="html.separate_file" value="true" />
+ <param key="sort_column" value="#5" />
+ <param key="hide_column" value="#7,#8,#9" />
+ <command>simplecomparison -query dominator_tree -options "-mode DIFF_RATIO_TO_FIRST" -baseline ${baseline}</command>
+ </query>
+ -->
+</section>
diff --git a/plugins/org.eclipse.mat.api/plugin.properties b/plugins/org.eclipse.mat.api/plugin.properties
index 7a7d467..aec98ad 100644
--- a/plugins/org.eclipse.mat.api/plugin.properties
+++ b/plugins/org.eclipse.mat.api/plugin.properties
@@ -23,11 +23,21 @@
overview.top_consumers = Top Consumers
overview.class_histogram = Class Histogram
+overview2.title = System Overview of Snapshots
+# Suffix used to build filename for report zip file - only translate if required
+overview2.suffix = Overview Delta
+overview2.dominator_tree = Dominator Tree
+
suspects.title = Leak Suspects
# Suffix used to build filename for report zip file - only translate if required
suspects.suffix = Leak Suspects
suspects.leaks = Leaks
+suspects2.title = Leak Suspects by Snapshot Comparison
+# Suffix used to build filename for report zip file - only translate if required
+suspects2.suffix = Leak Suspects Delta
+suspects2.leaks = Leaks Compared to Baseline
+
top_components.title = Top Components
# Suffix used to build filename for report zip file - only translate if required
top_components.suffix = Top Components
@@ -48,6 +58,12 @@
report.compare.name = __hidden__/Compare Snapshots
report.compare.help = Compare two snapshots and generate a histogram of differences in objects by class.
+report.suspects2.name = __hidden__/Leak Suspects by snapshot comparison
+report.suspects2.help = includes leak suspects and a system overview
+report.overview2.name = __hidden__/Heap Dump Overview comparison
+report.overview2.help = includes class histogram, system properties and top consumers.
+
+
contentType.java_heap_dump = Java Heap Dump
extension-point.factory.name = Snapshot Factory
diff --git a/plugins/org.eclipse.mat.api/plugin.xml b/plugins/org.eclipse.mat.api/plugin.xml
index 16d9c44..965e32d 100644
--- a/plugins/org.eclipse.mat.api/plugin.xml
+++ b/plugins/org.eclipse.mat.api/plugin.xml
Binary files differ
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/ComparisonReport.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/ComparisonReport.java
new file mode 100644
index 0000000..1373c16
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/ComparisonReport.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation.
+ * 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:
+ * Andrew Johnson (IBM Corporation) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mat.inspections;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.mat.query.IQuery;
+import org.eclipse.mat.query.IResult;
+import org.eclipse.mat.query.annotations.Argument;
+import org.eclipse.mat.query.annotations.Argument.Advice;
+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.SnapshotFactory;
+import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.IProgressListener;
+
+@Icon("/META-INF/icons/compare.gif")
+@HelpUrl("/org.eclipse.mat.ui.help/tasks/comparingdata.html")
+public class ComparisonReport implements IQuery
+{
+ @Argument
+ public ISnapshot snapshot;
+
+ @Argument(advice = Advice.SECONDARY_SNAPSHOT)
+ public ISnapshot baseline;
+
+ @Argument
+ public String report = "org.eclipse.mat.api:suspects2"; //$NON-NLS-1$
+
+ public IResult execute(IProgressListener listener) throws Exception
+ {
+ SnapshotQuery queryc = SnapshotQuery.parse("default_report "+report, snapshot); //$NON-NLS-1$
+ List<String> params = Collections.singletonList("baseline="+baseline.getSnapshotInfo().getPath()); //$NON-NLS-1$
+ queryc.setArgument("params", params); //$NON-NLS-1$
+ IResult ret = queryc.execute(listener);
+ return ret;
+ }
+}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery.java
index 84ec502..af7a290 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2018 SAP AG and IBM Corporation.
+ * Copyright (c) 2008, 2020 SAP AG and IBM Corporation.
* 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
@@ -7,6 +7,7 @@
*
* Contributors:
* SAP AG - initial API and implementation
+ * Andrew Johnson (IBM Corporation)- for comparisons
*******************************************************************************/
package org.eclipse.mat.inspections;
@@ -382,6 +383,11 @@
{
return object;
}
+
+ public long getRetainedHeapSize()
+ {
+ return getObject().getRetainedHeapSize();
+ }
}
public static class AccumulationPointOfGroupOfObject extends AccumulationPoint
@@ -546,11 +552,11 @@
return suspect.accumulationPoint == null ? Messages.FindLeaksQuery_NotFound : suspect.accumulationPoint.getObject()
.getTechnicalName();
case 5:
- return new Bytes(suspect.accumulationPoint == null ? 0 : suspect.accumulationPoint.getObject()
+ return new Bytes(suspect.accumulationPoint == null ? 0 : suspect.accumulationPoint
.getRetainedHeapSize());
case 6:
return suspect.accumulationPoint == null ? 0 : Double.valueOf((double) suspect.accumulationPoint
- .getObject().getRetainedHeapSize()
+ .getRetainedHeapSize()
/ (double) totalHeap);
}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery2.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery2.java
new file mode 100644
index 0000000..1113d14
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery2.java
@@ -0,0 +1,700 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2020 SAP AG and IBM Corporation.
+ * 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
+ * Andrew Johnson (IBM Corporation) - version for comparing two snapshots
+ *******************************************************************************/
+package org.eclipse.mat.inspections;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.runtime.Status;
+import org.eclipse.mat.SnapshotException;
+import org.eclipse.mat.collect.ArrayInt;
+import org.eclipse.mat.collect.ArrayIntBig;
+import org.eclipse.mat.collect.BitField;
+import org.eclipse.mat.collect.HashMapIntObject;
+import org.eclipse.mat.inspections.FindLeaksQuery.SuspectRecord;
+import org.eclipse.mat.internal.MATPlugin;
+import org.eclipse.mat.internal.Messages;
+import org.eclipse.mat.query.Bytes;
+import org.eclipse.mat.query.Column;
+import org.eclipse.mat.query.Column.SortDirection;
+import org.eclipse.mat.query.ContextProvider;
+import org.eclipse.mat.query.IContextObject;
+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.Category;
+import org.eclipse.mat.query.annotations.CommandName;
+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.results.CompositeResult;
+import org.eclipse.mat.snapshot.ClassHistogramRecord;
+import org.eclipse.mat.snapshot.ClassLoaderHistogramRecord;
+import org.eclipse.mat.snapshot.Histogram;
+import org.eclipse.mat.snapshot.IMultiplePathsFromGCRootsComputer;
+import org.eclipse.mat.snapshot.ISnapshot;
+import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsRecord;
+import org.eclipse.mat.snapshot.model.IClass;
+import org.eclipse.mat.snapshot.model.IObject;
+import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.IProgressListener;
+import org.eclipse.mat.util.MessageUtil;
+
+import com.ibm.icu.text.NumberFormat;
+
+@CommandName("find_leaks2")
+@Category(Category.HIDDEN)
+@Icon("/META-INF/icons/leak.gif")
+public class FindLeaksQuery2 implements IQuery
+{
+
+ // ///////////////////////////////////////////
+ //
+ // static fields
+ //
+ // ///////////////////////////////////////////
+
+ private final static Set<String> REFERENCE_FIELD_SET = new HashSet<String>(Arrays
+ .asList(new String[] { "referent" })); //$NON-NLS-1$
+ private final static int MAX_DEPTH = 1000;
+
+ // ////////////////////////////////////////////
+ //
+ // Command parameters
+ //
+ // ////////////////////////////////////////////
+
+ @Argument
+ public ISnapshot snapshot;
+
+ @Argument(advice = Advice.SECONDARY_SNAPSHOT)
+ public ISnapshot baseline;
+
+ @Argument(isMandatory = false)
+ public int threshold_percent = 2;
+
+ @Argument(isMandatory = false)
+ public int max_paths = 10000;
+
+ // @Argument(isMandatory = false, flag = "big_drop_ratio")
+ public double big_drop_ratio = 0.7;
+
+ @Argument(isMandatory = false)
+ public String options = "-prefix"; //$NON-NLS-1$
+
+ @Argument(isMandatory = false)
+ public Pattern mask = Pattern.compile("\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$|(?<=\\p{javaJavaIdentifierPart}\\[)\\d+(?=\\])"); //$NON-NLS-1$
+
+ @Argument(isMandatory = false, flag = "x")
+ public String[] extraReferences = new String[] {
+ "java.util.HashMap$Node:key", //$NON-NLS-1$
+ "java.util.Hashtable$Entry:key", //$NON-NLS-1$
+ "java.util.WeakHashMap$Entry:referent", //$NON-NLS-1$
+ "java.util.concurrent.ConcurrentHashMap$Node:key" //$NON-NLS-1$
+ };
+
+ @Argument(isMandatory = false, flag = "xfile")
+ public File extraReferencesListFile;
+
+ static final int retainedDiffCol = 5;
+ static final int simpleDiffCol = 2;
+
+ public IResult execute(IProgressListener listener) throws Exception
+ {
+ long totalHeap;
+
+ totalHeap = baseline.getSnapshotInfo().getUsedHeapSize();
+ long threshold = threshold_percent * totalHeap / 100;
+
+ IResultTree baseTree = callDominatorTree(listener, baseline);
+ IResultTree currTree = callDominatorTree(listener, snapshot);
+
+ String queryId = "comparetablesquery -mode DIFF_RATIO_TO_FIRST"; //$NON-NLS-1$
+ if (options != null && options.length() > 0)
+ queryId += " " + options; //$NON-NLS-1$
+
+ SnapshotQuery queryc = SnapshotQuery.parse(queryId, snapshot);
+
+ List<IResultTree> r = new ArrayList<IResultTree>();
+ r.add(baseTree);
+ r.add(currTree);
+ queryc.setArgument("tables", r); //$NON-NLS-1$
+ ArrayList<ISnapshot> snapshots = new ArrayList<ISnapshot>();
+ snapshots.add(baseline);
+ snapshots.add(snapshot);
+ queryc.setArgument("snapshots", snapshots); //$NON-NLS-1$
+
+ if (mask != null && mask.pattern().length() > 0)
+ queryc.setArgument("mask", mask); //$NON-NLS-1$
+ if (extraReferences != null && extraReferences.length > 0)
+ queryc.setArgument("extraReferences", Arrays.asList(extraReferences)); //$NON-NLS-1$
+ if (extraReferencesListFile != null)
+ queryc.setArgument("extraReferencesListFile", extraReferencesListFile); //$NON-NLS-1$
+
+ RefinedResultBuilder rbc = queryc.refine(listener);
+ rbc.setSortOrder(retainedDiffCol, SortDirection.DESC);
+ final RefinedTree compTree = (RefinedTree)rbc.build();
+ List<?>topDominators = compTree.getElements();
+
+ /*
+ * find suspect single objects
+ */
+ listener.subTask(Messages.FindLeaksQuery_SearchingSingleObjects);
+
+ List<ContextProvider> provs = compTree.getResultMetaData().getContextProviders();
+ // Get the last - in case the first is also this snapshot, so has a context provider
+ ContextProvider cp = provs.get(provs.size() - 1);
+ ArrayInt suspiciousObjects = new ArrayInt();
+ Set<String> suspectNames = new HashSet<String>();
+ int i = 0;
+ HashMapIntObject<ClassRecord>map = new HashMapIntObject<ClassRecord>();
+ final HashMapIntObject<Object>rowmap = new HashMapIntObject<Object>();
+ while (i < topDominators.size())
+ {
+ Object row = topDominators.get(i);
+ IContextObject ctx = cp.getContext(row);
+ if (ctx != null)
+ {
+ int objId = ctx.getObjectId();
+ if (objId >= 0)
+ {
+ long deltaRetained = readCol(compTree, row, retainedDiffCol);
+ IClass cls = snapshot.getClassOf(objId);
+ if (deltaRetained > threshold)
+ {
+ suspiciousObjects.add(objId);
+ suspectNames.add(cls.getName());
+ rowmap.put(objId, row);
+ }
+ else
+ {
+ // Add to types
+ ClassRecord cr = map.get(cls.getObjectId());
+ if (cr == null)
+ {
+ cr = new ClassRecord(cls.getName(), cls.getObjectId());
+ map.put(cls.getObjectId(), cr);
+ }
+ long deltaSimple = readCol(compTree, row, simpleDiffCol);
+ cr.addObj(objId, deltaSimple, deltaRetained);
+ rowmap.put(objId, row);
+ }
+ }
+ }
+ i++;
+ }
+
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ /*
+ * Find suspect classes
+ */
+ listener.subTask(Messages.FindLeaksQuery_SearchingGroupsOfObjects);
+
+ ArrayList<ClassHistogramRecord> suspiciousClasses = new ArrayList<ClassHistogramRecord>();
+
+ for (Iterator<ClassRecord> it = map.values(); it.hasNext(); )
+ {
+ ClassRecord cr = it.next();
+ if (cr.retained > threshold)
+ {
+ ClassHistogramRecord chr = new ClassHistogramRecord(cr.name, cr.clsId, cr.objs.toArray(), cr.simple, cr.retained);
+ suspiciousClasses.add(chr);
+ }
+ }
+
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ /*
+ * build the results
+ */
+ final SuspectsResultTable ret = buildResult(suspiciousObjects, suspiciousClasses, totalHeap, compTree, rowmap, listener);
+
+ // Indicate some interesting rows
+ compTree.setSelectionProvider(new ISelectionProvider() {
+
+ public boolean isSelected(Object row)
+ {
+ if (row == null)
+ return false;
+ List<ContextProvider> provs = compTree.getResultMetaData().getContextProviders();
+ IContextObject co = provs.get(provs.size() - 1).getContext(row);
+ if (co == null)
+ return false;
+ int objId = co.getObjectId();
+ if (objId < 0)
+ return false;
+ for (SuspectRecord sr : ret.getData())
+ {
+ if (sr instanceof FindLeaksQuery.SuspectRecordGroupOfObjects)
+ {
+ // This is a group of objects, so check each one
+ FindLeaksQuery.SuspectRecordGroupOfObjects srg = (FindLeaksQuery.SuspectRecordGroupOfObjects)sr;
+ for (int o1 : srg.getSuspectInstances())
+ {
+ if (objId == o1)
+ return true;
+ }
+ /*
+ * Not required to check the suspect ID as that is the class
+ * but the dominator tree will be instances.
+ */
+ }
+ else
+ {
+ if (objId == sr.getSuspect().getObjectId())
+ return true;
+ }
+ org.eclipse.mat.inspections.FindLeaksQuery.AccumulationPoint accumulationPoint = sr.getAccumulationPoint();
+ if (accumulationPoint != null && objId == accumulationPoint.getObject().getObjectId())
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isExpanded(Object row)
+ {
+ /* How deep - HtmlOutputter has a limit of 100, screen 25 */
+ final int MAX_EXPAND = 30;
+ if (row == null)
+ return false;
+ List<ContextProvider> provs = compTree.getResultMetaData().getContextProviders();
+ IContextObject co = provs.get(provs.size() - 1).getContext(row);
+ if (co == null)
+ return false;
+ int objId = co.getObjectId();
+ if (objId < 0)
+ return false;
+ for (SuspectRecord sr : ret.getData())
+ {
+ if (sr instanceof FindLeaksQuery.SuspectRecordGroupOfObjects)
+ {
+ FindLeaksQuery.SuspectRecordGroupOfObjects srg = (FindLeaksQuery.SuspectRecordGroupOfObjects)sr;
+ for (int i = 0; i < Math.min(srg.getCommonPath().length - 1, MAX_EXPAND); ++i)
+ {
+ if (objId == srg.getCommonPath()[i])
+ return true;
+ }
+ }
+ org.eclipse.mat.inspections.FindLeaksQuery.AccumulationPoint accumulationPoint = sr.getAccumulationPoint();
+ if (accumulationPoint instanceof AccumulationPoint)
+ {
+ AccumulationPoint ap2 = (AccumulationPoint)accumulationPoint;
+ for (int i = 0; i < Math.min(ap2.getPath().length - 1, MAX_EXPAND); ++i)
+ {
+ if (objId == ap2.getPath()[i])
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ });
+ CompositeResult cr = new CompositeResult();
+ cr.addResult(Messages.FindLeaksQuery2_ComparedDominatorTrees, compTree);
+ cr.addResult(Messages.FindLeaksQuery2_Leaks, ret);
+ return cr;
+ }
+
+ /**
+ * Read a column cell from the compared tree.
+ * @param compTree
+ * @param row
+ * @param retainedDiffCol
+ * @return
+ */
+ private long readCol(IResultTree compTree, final Object row, final int retainedDiffCol)
+ {
+ Object colv = compTree.getColumnValue(row, retainedDiffCol);
+ long deltaRetained;
+ if (colv instanceof Bytes)
+ deltaRetained = ((Bytes)colv).getValue();
+ else if (colv instanceof Number)
+ deltaRetained = ((Number)colv).longValue();
+ else
+ deltaRetained = 0;
+ return deltaRetained;
+ }
+
+ private static class ClassRecord
+ {
+ String name;
+ int clsId;
+ ArrayInt objs;
+ long simple;
+ long retained;
+ public ClassRecord(String name, int id)
+ {
+ this.name = name;
+ clsId = id;
+ objs = new ArrayInt();
+ }
+
+ public void addObj(int obj, long simple, long retained)
+ {
+ objs.add(obj);
+ this.simple += simple;
+ this.retained += retained;
+ }
+ }
+
+ private IResultTree callDominatorTree(IProgressListener listener, ISnapshot snapshot) throws Exception
+ {
+ return (IResultTree) SnapshotQuery.lookup("dominator_tree", snapshot) //$NON-NLS-1$
+ .execute(listener);
+ }
+
+ private Histogram groupByClasses(int[] dominated, IProgressListener listener) throws SnapshotException
+ {
+ Histogram histogram = snapshot.getHistogram(dominated, listener);
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ Collection<ClassHistogramRecord> records = histogram.getClassHistogramRecords();
+ ClassHistogramRecord[] arr = new ClassHistogramRecord[records.size()];
+ int i = 0;
+
+ for (ClassHistogramRecord record : records)
+ {
+ record.setRetainedHeapSize(sumRetainedSize(record.getObjectIds(), snapshot));
+ arr[i++] = record;
+ if (i % 10 == 0 && listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+ }
+
+ Collection<ClassLoaderHistogramRecord> loaderRecords = histogram.getClassLoaderHistogramRecords();
+ ClassLoaderHistogramRecord[] loaderArr = new ClassLoaderHistogramRecord[loaderRecords.size()];
+ i = 0;
+
+ for (ClassLoaderHistogramRecord record : loaderRecords)
+ {
+ long retainedSize = 0;
+ for (ClassHistogramRecord classRecord : record.getClassHistogramRecords())
+ {
+ retainedSize += classRecord.getRetainedHeapSize();
+ }
+
+ record.setRetainedHeapSize(retainedSize);
+ loaderArr[i++] = record;
+ }
+
+ return histogram;
+
+ }
+
+ private long sumRetainedSize(int[] objectIds, ISnapshot snapshot) throws SnapshotException
+ {
+ long sum = 0;
+ for (int id : objectIds)
+ {
+ sum += snapshot.getRetainedHeapSize(id);
+ }
+ return sum;
+ }
+
+ /**
+ * Find the accumulation point by big drops in the delta size
+ * @param bigObjectId
+ * @param tree
+ * @param row
+ * @return
+ * @throws SnapshotException
+ */
+ private FindLeaksQuery.AccumulationPoint findAccumulationPoint(int bigObjectId, IResultTree tree, Object row) throws SnapshotException
+ {
+ int dominator = bigObjectId;
+ ArrayInt path = new ArrayInt();
+ path.add(dominator);
+ List<?> rows = null;
+ long dominatorRetainedSize = readCol(tree, row, retainedDiffCol);
+
+ List<ContextProvider> provs = tree.getResultMetaData().getContextProviders();
+ ContextProvider cp = provs.get(provs.size() - 1);
+ int depth = 0;
+ while (tree.hasChildren(row) && (rows = tree.getChildren(row)) != null && rows.size() != 0 && depth < MAX_DEPTH)
+ {
+ long dominatedRetainedSize = this.readCol(tree, rows.get(0), retainedDiffCol);
+ if ((double)dominatedRetainedSize / dominatorRetainedSize < big_drop_ratio)
+ {
+ return new AccumulationPoint(snapshot.getObject(dominator), dominatorRetainedSize, path.toArray());
+ }
+
+ dominatorRetainedSize = dominatedRetainedSize;
+ row = rows.get(0);
+ // Should be a context if there was a retained size
+ dominator = cp.getContext(row).getObjectId();
+ path.add(dominator);
+ depth++;
+ }
+
+ if (rows == null || rows.size() == 0)
+ return new AccumulationPoint(snapshot.getObject(dominator), dominatorRetainedSize, path.toArray());
+
+ return null;
+ }
+
+ private FindLeaksQuery.SuspectRecord buildSuspectRecordGroupOfObjects(ClassHistogramRecord record, IResultTree tree, HashMapIntObject<Object>rowmap, IProgressListener listener)
+ throws SnapshotException
+ {
+ int[] objectIds = getRandomIds(record.getObjectIds());
+ IObject suspectClass = snapshot.getObject(record.getClassId());
+ List<ContextProvider> provs = tree.getResultMetaData().getContextProviders();
+
+ // calculate the shortest paths to all
+ // avoid weak paths
+ Map<IClass, Set<String>> excludeMap = new HashMap<IClass, Set<String>>();
+ Collection<IClass> classes = snapshot.getClassesByName("java.lang.ref.WeakReference", true); //$NON-NLS-1$
+ if (classes != null)
+ for (IClass clazz : classes)
+ {
+ excludeMap.put(clazz, REFERENCE_FIELD_SET);
+ }
+
+ IMultiplePathsFromGCRootsComputer comp = snapshot.getMultiplePathsFromGCRoots(objectIds, excludeMap);
+
+ MultiplePathsFromGCRootsRecord[] records = comp.getPathsByGCRoot(listener);
+ ArrayIntBig commonPath = new ArrayIntBig();
+
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ int numPaths = comp.getAllPaths(listener).length;
+ int diff = objectIds.length - numPaths;
+ if (diff > 0)
+ {
+ listener.sendUserMessage(IProgressListener.Severity.INFO, MessageUtil.format(
+ Messages.FindLeaksQuery_PathNotFound, diff, objectIds.length), null);
+ }
+ setRetainedSizesForMPaths(records, snapshot);
+ Arrays.sort(records, MultiplePathsFromGCRootsRecord.getComparatorByNumberOfReferencedObjects());
+
+ MultiplePathsFromGCRootsRecord parentRecord = records[0];
+
+ // parentRecord.getReferencedRetainedSize()
+ int threshold = (int) (0.8 * objectIds.length);
+
+ Object row = null;
+ while (parentRecord.getCount() > threshold)
+ {
+ // System.out.println("count: " + parentRecord.getCount());
+ commonPath.add(parentRecord.getObjectId());
+
+ // Try to match path in dominator tree
+ if (row == null)
+ {
+ // possibly find in dominator tree
+ row = rowmap.get(parentRecord.getObjectId());
+ }
+
+ MultiplePathsFromGCRootsRecord[] children = parentRecord.nextLevel();
+ if (children == null || children.length == 0)
+ {
+ // reached the end ?! report the parent as it is big enough
+ int path[] = commonPath.toArray();
+ FindLeaksQuery.AccumulationPoint accPoint = new FindLeaksQuery.AccumulationPoint(snapshot.getObject(parentRecord.getObjectId()));
+ if (rowmap.get(parentRecord.getObjectId()) == row)
+ {
+ // Row is current, so use delta size
+ long deltaRetained = readCol(tree, row, retainedDiffCol);
+ accPoint = new AccumulationPoint(snapshot.getObject(parentRecord.getObjectId()), deltaRetained, path);
+ }
+ FindLeaksQuery.SuspectRecordGroupOfObjects result = new FindLeaksQuery.SuspectRecordGroupOfObjects(suspectClass, record
+ .getObjectIds(), record.getRetainedHeapSize(), accPoint, commonPath.toArray(), comp);
+ return result;
+ }
+ setRetainedSizesForMPaths(children, snapshot);
+ Arrays.sort(children, MultiplePathsFromGCRootsRecord.getComparatorByNumberOfReferencedObjects());
+
+ long childReferencedRetainedSize = children[0].getReferencedRetainedSize();
+
+ // Match child?
+ if (row != null && tree.hasChildren(row))
+ {
+ //System.out.println("Finding ");
+ for (Object row2 : tree.getChildren(row))
+ {
+ IContextObject co = provs.get(provs.size() - 1).getContext(row2);
+ if (co != null && co.getObjectId() == children[0].getObjectId())
+ {
+ // Found again
+ row = row2;
+ //System.out.println("Found again "+parentRecord.getObjectId() + " " + row);
+ break;
+ }
+ }
+ }
+ if ((double) childReferencedRetainedSize / (double) parentRecord.getReferencedRetainedSize() < big_drop_ratio)
+ {
+ // there is a big drop here - return the parent
+ int path[] = commonPath.toArray();
+ FindLeaksQuery.AccumulationPoint accPoint = new FindLeaksQuery.AccumulationPoint(snapshot.getObject(parentRecord.getObjectId()));
+ if (rowmap.get(parentRecord.getObjectId()) == row)
+ {
+ // Row is current, so use delta size
+ long deltaRetained = readCol(tree, row, retainedDiffCol);
+ accPoint = new AccumulationPoint(snapshot.getObject(parentRecord.getObjectId()), deltaRetained, path);
+ }
+ FindLeaksQuery.SuspectRecordGroupOfObjects result = new FindLeaksQuery.SuspectRecordGroupOfObjects(suspectClass, record
+ .getObjectIds(), record.getRetainedHeapSize(), accPoint, path, comp);
+ return result;
+ }
+
+ // no big drop - take the biggest child and try again
+ parentRecord = children[0];
+ }
+
+ // return a SuspectRecord without an accumulation point
+ return new FindLeaksQuery.SuspectRecordGroupOfObjects(suspectClass, record.getObjectIds(), record.getRetainedHeapSize(), null,
+ commonPath.toArray(), comp);
+ }
+
+ private void setRetainedSizesForMPaths(MultiplePathsFromGCRootsRecord[] records, ISnapshot snapshot)
+ throws SnapshotException
+ {
+ for (MultiplePathsFromGCRootsRecord rec : records)
+ {
+ int[] referencedObjects = rec.getReferencedObjects();
+ long retained = 0;
+ for (int objectId : referencedObjects)
+ {
+ retained += snapshot.getRetainedHeapSize(objectId);
+ }
+ rec.setReferencedRetainedSize(retained);
+ }
+ }
+
+ private SuspectsResultTable buildResult(ArrayInt suspiciousObjects, ArrayList<ClassHistogramRecord> suspiciousClasses,
+ long totalHeap, IResultTree tree, HashMapIntObject<Object>rowmap, IProgressListener listener) throws SnapshotException
+ {
+ FindLeaksQuery.SuspectRecord[] allSuspects = new FindLeaksQuery.SuspectRecord[suspiciousObjects.size() + suspiciousClasses.size()];
+ int j = 0;
+ int[] suspectObjIds = suspiciousObjects.toArray();
+ for (int objectId : suspectObjIds)
+ {
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ IObject suspectObject = snapshot.getObject(objectId);
+ FindLeaksQuery.AccumulationPoint accPoint = findAccumulationPoint(objectId, tree, rowmap.get(objectId));
+ long suspectObjectRetained = suspectObject.getRetainedHeapSize();
+ Object row = rowmap.get(objectId);
+ long deltaRetained = readCol(tree, row, retainedDiffCol);
+ suspectObjectRetained = deltaRetained;
+ FindLeaksQuery.SuspectRecord r = new FindLeaksQuery.SuspectRecord(suspectObject, suspectObjectRetained, accPoint);
+
+ allSuspects[j++] = r;
+ }
+
+ for (ClassHistogramRecord record : suspiciousClasses)
+ {
+ if (listener.isCanceled())
+ throw new IProgressListener.OperationCanceledException();
+
+ FindLeaksQuery.SuspectRecord r = buildSuspectRecordGroupOfObjects(record, /*
+ * (long)
+ * (
+ * threshold
+ * 0.7),
+ */tree, rowmap, listener);
+ allSuspects[j++] = r;
+ }
+
+ return new SuspectsResultTable(allSuspects, totalHeap);
+ }
+
+ private int[] getRandomIds(int[] objectIds)
+ {
+ if (objectIds.length <= max_paths)
+ return objectIds;
+
+ MATPlugin.log(new Status(Status.INFO, MATPlugin.PLUGIN_ID, MessageUtil.format(
+ Messages.FindLeaksQuery_TooManySuspects,
+ objectIds.length, max_paths)));
+
+ Random random = new Random();
+ int length = objectIds.length;
+ BitField visited = new BitField(length);
+
+ int[] result = new int[max_paths];
+ for (int i = 0; i < max_paths; i++)
+ {
+ int index = random.nextInt(length);
+ while (visited.get(index))
+ index = random.nextInt(length);
+
+ visited.set(index);
+ result[i] = objectIds[index];
+ }
+ return result;
+ }
+
+ public static class AccumulationPoint extends FindLeaksQuery.AccumulationPoint
+ {
+ long retainedSize;
+ int path[];
+
+ public AccumulationPoint(IObject object, long retainedSize, int path[])
+ {
+ super(object);
+ this.retainedSize = retainedSize;
+ this.path = path;
+ }
+
+ public long getRetainedHeapSize()
+ {
+ return this.retainedSize;
+ }
+
+ public int[] getPath()
+ {
+ return path;
+ }
+ }
+
+ public static class SuspectsResultTable extends FindLeaksQuery.SuspectsResultTable
+ {
+
+ public SuspectsResultTable(SuspectRecord[] data, long totalHeap)
+ {
+ super(data, totalHeap);
+ }
+
+ public Column[] getColumns()
+ {
+ return new Column[] { new Column(Messages.FindLeaksQuery_ColumnLeakSuspect), //
+ new Column(Messages.FindLeaksQuery_Column_NumObjects, Long.class), //
+ new Column(Messages.FindLeaksQuery2_Column_SuspectRetainedHeap, Bytes.class), //
+ new Column(Messages.FindLeaksQuery_Column_SuspectPercent, Double.class).formatting(NumberFormat.getPercentInstance()), //
+ new Column(Messages.FindLeaksQuery_Column_AccumulationPoint), //
+ new Column(Messages.FindLeaksQuery2_Column_AccPointRetainedHeap, Bytes.class), //
+ new Column(Messages.FindLeaksQuery_Column_AccPointPercent, Double.class).formatting(NumberFormat.getPercentInstance()) };
+ }
+
+ }
+
+}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
index 69228bf..50cde92 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
@@ -189,7 +189,7 @@
}
}
- private FindLeaksQuery.SuspectsResultTable callFindLeaks(IProgressListener listener) throws Exception
+ 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$
@@ -348,7 +348,7 @@
objectsForTroubleTicketInfo.add(accPointClassloader);
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
- overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedBy, classloaderName));
+ overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedBy, classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else if (snapshot.isClass(accumulationPointId))
@@ -362,7 +362,7 @@
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByLoadedBy, HTMLUtils.escapeText(clazz.getName()),
- classloaderName));
+ classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else
{
@@ -376,7 +376,7 @@
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByInstance, HTMLUtils.escapeText(className),
- classloaderName));
+ classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
}
@@ -507,7 +507,7 @@
involvedClassLoaders.add((IClassLoader) suspect.getAccumulationPoint().getObject());
classloaderName = getClassLoaderName(suspect.getAccumulationPoint().getObject(), keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromClassLoader,
- classloaderName));
+ classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else if (snapshot.isClass(accumulationPointId))
{
@@ -521,7 +521,7 @@
classloaderName = getClassLoaderName(accPointClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromClass, className,
- classloaderName));
+ classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
else
{
@@ -536,7 +536,7 @@
classloaderName = getClassLoaderName(accPointClassloader, keywords);
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedFromInstance, className,
- classloaderName));
+ classloaderName, formatRetainedHeap(suspect.getAccumulationPoint().getRetainedHeapSize(), totalHeap)));
}
}
builder.append("<br><br>"); //$NON-NLS-1$
@@ -662,7 +662,7 @@
try
{
Collection<IClass> classesByName = ic.getSnapshot().getClassesByName(className, false);
- if (className.matches("[\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}.]+") && classesByName != null && classesByName.size() == 1) //$NON-NLS-1$
+ if (className.matches("\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}.]*") && classesByName != null && classesByName.size() == 1) //$NON-NLS-1$
{
return className;
}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery2.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery2.java
new file mode 100644
index 0000000..54397d2
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery2.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation.
+ * 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:
+ * Andrew Johnson (IBM Corporation) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mat.inspections;
+
+import java.io.File;
+import java.text.Format;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+import org.eclipse.mat.query.BytesFormat;
+import org.eclipse.mat.query.IResult;
+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.Icon;
+import org.eclipse.mat.query.results.CompositeResult;
+import org.eclipse.mat.report.Params;
+import org.eclipse.mat.report.QuerySpec;
+import org.eclipse.mat.report.SectionSpec;
+import org.eclipse.mat.snapshot.Histogram;
+import org.eclipse.mat.snapshot.ISnapshot;
+import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.IProgressListener;
+
+/**
+ * Looks for leaks based on a delta in retained sizes of
+ * the dominator tree from two snapshots.
+ */
+@CommandName("leakhunter2")
+@Icon("/META-INF/icons/leak.gif")
+public class LeakHunterQuery2 extends LeakHunterQuery
+{
+
+ @Argument(advice = Advice.SECONDARY_SNAPSHOT)
+ public ISnapshot baseline;
+
+ @Argument(isMandatory = false)
+ public String options = "-prefix"; //$NON-NLS-1$
+
+ @Argument(isMandatory = false)
+ public Pattern mask = Pattern.compile("\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$|(?<=\\p{javaJavaIdentifierPart}\\[)\\d+(?=\\])"); //$NON-NLS-1$
+
+ @Argument(isMandatory = false, flag = "x")
+ public String[] extraReferences = new String[] {
+ "java.util.HashMap$Node:key", //$NON-NLS-1$
+ "java.util.Hashtable$Entry:key", //$NON-NLS-1$
+ "java.util.WeakHashMap$Entry:referent", //$NON-NLS-1$
+ "java.util.concurrent.ConcurrentHashMap$Node:key" //$NON-NLS-1$
+ };
+
+ @Argument(isMandatory = false, flag = "xfile")
+ public File extraReferencesListFile;
+
+ public IResult execute(IProgressListener listener) throws Exception
+ {
+ // Get a signed bytes formatter
+ Histogram dummy = snapshot.getHistogram(new int[0], listener);
+ dummy = dummy.diffWithBaseline(dummy);
+ Format f = dummy.getColumns()[2].getFormatter();
+ if (f instanceof BytesFormat)
+ bytesFormatter = (BytesFormat)f;
+
+ IResult res = super.execute(listener);
+ if (res instanceof SectionSpec)
+ {
+ // Add in saved dominator tree
+ QuerySpec spec = new QuerySpec(savedResult.getName());
+ spec.setResult(savedResult.getResult());
+ spec.setCommand(savedcmd);
+ spec.set(Params.Html.COLLAPSED, Boolean.TRUE.toString());
+ spec.set(Params.Rendering.SORT_COLUMN, "#5"); //$NON-NLS-1$
+ spec.set(Params.Rendering.HIDE_COLUMN, "#7,#8,#9"); //$NON-NLS-1$
+ ((SectionSpec) res).add(spec);
+ }
+ return res;
+ }
+
+ CompositeResult.Entry savedResult;
+ String savedcmd;
+
+ FindLeaksQuery.SuspectsResultTable callFindLeaks(IProgressListener listener) throws Exception
+ {
+ String querycmd = "find_leaks2"; //$NON-NLS-1$
+ if (options != null)
+ querycmd += " -options " + options; //$NON-NLS-1$
+ StringBuilder cmd = new StringBuilder(querycmd);
+ SnapshotQuery query = SnapshotQuery.parse(querycmd, snapshot)
+ .setArgument("threshold_percent", threshold_percent) //$NON-NLS-1$
+ .setArgument("max_paths", max_paths) //$NON-NLS-1$
+ .setArgument("baseline", baseline); //$NON-NLS-1$
+
+ cmd.append(" -threshold_percent ").append(threshold_percent); //$NON-NLS-1$
+ cmd.append(" -max_paths ").append(max_paths); //$NON-NLS-1$
+ cmd.append(" -baseline ").append(baseline.getSnapshotInfo().getPath()); //$NON-NLS-1$
+ if (mask != null)
+ {
+ query.setArgument("mask", mask); //$NON-NLS-1$
+ cmd.append(" ").append("-mask ").append(escape(mask.pattern())); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (extraReferences != null)
+ {
+ query.setArgument("extraReferences", Arrays.asList(extraReferences)); //$NON-NLS-1$
+ cmd.append(" ").append("-x "); //$NON-NLS-1$ //$NON-NLS-2$
+ for (String e : extraReferences)
+ cmd.append(" ").append(escape(e)); //$NON-NLS-1$
+ }
+ if (extraReferencesListFile != null)
+ {
+ query.setArgument("extraReferencesListFile", extraReferencesListFile); //$NON-NLS-1$
+ cmd.append(" -xfile ").append(escape(extraReferencesListFile.getAbsolutePath())); //$NON-NLS-1$
+ }
+
+ savedcmd = cmd.toString();
+ IResult ret = query.execute(listener);
+ if (ret instanceof CompositeResult)
+ {
+ CompositeResult cr = (CompositeResult)ret;
+ // The delta dominator tree, save for later.
+ savedResult = cr.getResultEntries().get(0);
+ // The leaks, pass back to LinkHunterQuery.
+ return (FindLeaksQuery.SuspectsResultTable)cr.getResultEntries().get(1).getResult();
+ }
+ else
+ {
+ return (FindLeaksQuery.SuspectsResultTable)ret;
+ }
+ }
+
+ static String escape(String s)
+ {
+ /*
+ * abc\def -> abc\def
+ * abc"def -> abc\"def
+ * abc\\def -> abc\\\def
+ * abc\"def -> abc\\\"def
+ */
+ s = s.replaceAll("\\\\(?=\\\\)|\\\\(?=\")|\"", "\\\\$0"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (s.indexOf(' ') >= 0)
+ return "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+ return s;
+ }
+
+}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/SimpleComparison.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/SimpleComparison.java
new file mode 100644
index 0000000..40302f4
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/SimpleComparison.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation.
+ * 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:
+ * Andrew Johnson (IBM Corporation) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mat.inspections;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.mat.query.IQuery;
+import org.eclipse.mat.query.IResult;
+import org.eclipse.mat.query.IStructuredResult;
+import org.eclipse.mat.query.annotations.Argument;
+import org.eclipse.mat.query.annotations.Argument.Advice;
+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.snapshot.ISnapshot;
+import org.eclipse.mat.snapshot.query.RetainedSizeDerivedData;
+import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.IProgressListener;
+
+/**
+ * A simple comparison of the results of running a query on two
+ * different snapshots.
+ */
+@Icon("/META-INF/icons/compare.gif")
+@HelpUrl("/org.eclipse.mat.ui.help/tasks/comparingdata.html")
+public class SimpleComparison implements IQuery
+{
+ @Argument
+ public ISnapshot snapshot;
+
+ @Argument(advice = Advice.SECONDARY_SNAPSHOT)
+ public ISnapshot baseline;
+
+ @Argument
+ public String query;
+
+ @Argument(isMandatory = false)
+ public String options;
+
+ @Argument(isMandatory = false)
+ public String defaultoptions = "-mask \"\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$|(?<=\\p{javaJavaIdentifierPart}\\[)\\d+(?=\\])\" -x java.util.HashMap$Node:key java.util.Hashtable$Entry:key java.util.WeakHashMap$Entry:referent java.util.concurrent.ConcurrentHashMap$Node:key"; //$NON-NLS-1$
+
+ public enum Retained {
+ APPROXIMATE,
+ PRECISE
+ };
+
+ @Argument(isMandatory = false)
+ public Retained retained;
+
+ public IResult execute(IProgressListener listener) throws Exception
+ {
+ IStructuredResult baseTable = callQuery(listener, baseline);
+ IStructuredResult currTable = callQuery(listener, snapshot);
+
+ String queryId = "comparetablesquery"; //$NON-NLS-1$
+ if (options != null && options.length() > 0)
+ queryId += " " + options; //$NON-NLS-1$
+ if (defaultoptions != null && defaultoptions.length() > 0)
+ queryId += " " + defaultoptions; //$NON-NLS-1$
+
+ SnapshotQuery queryc = SnapshotQuery.parse(queryId, snapshot);
+
+ List<IStructuredResult> r = new ArrayList<IStructuredResult>();
+ r.add(baseTable);
+ r.add(currTable);
+ queryc.setArgument("tables", r); //$NON-NLS-1$
+ ArrayList<ISnapshot> snapshots = new ArrayList<ISnapshot>();
+ snapshots.add(baseline);
+ snapshots.add(snapshot);
+ queryc.setArgument("snapshots", snapshots); //$NON-NLS-1$
+ IResult ret = queryc.execute(listener);
+ listener.done();
+ return ret;
+ }
+
+ private IStructuredResult callQuery(IProgressListener listener, ISnapshot snapshot) throws Exception
+ {
+ if (retained != null)
+ {
+ RefinedResultBuilder rb1 = SnapshotQuery.parse(query, snapshot).refine(listener);
+ rb1.setInlineRetainedSizeCalculation(true);
+ rb1.addDefaultContextDerivedColumn(retained == Retained.PRECISE ? RetainedSizeDerivedData.PRECISE : RetainedSizeDerivedData.APPROXIMATE);
+ return rb1.build();
+ }
+ else
+ {
+ return (IStructuredResult) SnapshotQuery.parse(query, snapshot)
+ .execute(listener);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/annotations.properties b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/annotations.properties
index 0e270c3..4837a26 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/annotations.properties
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/annotations.properties
@@ -40,6 +40,14 @@
ClassReferrersQuery.inbound.help = When a class group is expanded find all the objects pointing to any object in the group \
and then group those objects by class.
+ComparisonReport.name = Compare Snapshots Leak Report
+ComparisonReport.category = Leak Identification
+ComparisonReport.help = Runs a report designed to compare two snapshots.\
+The default report includes leak suspects based on the growth in memory usage \
+and a system overview comparison.
+ComparisonReport.report.help = The report identifier.
+ComparisonReport.baseline.help = The baseline snapshot filename.
+
CustomizedRetainedSetQuery.name = Customized Retained Set
CustomizedRetainedSetQuery.category = Java Basics
CustomizedRetainedSetQuery.help = Calculate the retained set of objects excluding references via given fields.\n\n\
@@ -69,7 +77,7 @@
DeltaHistogram.name = Compare Snapshots
DeltaHistogram.help = Compare the histograms of two snapshots by generating a difference histogram.
-DeltaHistogram.snapshot2.help = The second snapshot file.
+DeltaHistogram.snapshot2.help = The baseline snapshot file.
DuplicatedClassesQuery.name = Duplicate Classes
DuplicatedClassesQuery.category = Java Basics
@@ -105,6 +113,10 @@
HeapDumpInfoQuery.name = Heap Dump Overview
HeapDumpInfoQuery.help = Displays heap dump details: number of objects, etc.
+HeapDumpInfoQuery2.name = Heap Dump Overview comparison
+HeapDumpInfoQuery2.help = Displays heap dump details: number of objects, etc.
+HeapDumpInfoQuery2.baseline.help = The baseline snapshot file.
+
HistogramQuery.name = Show As Histogram
HistogramQuery.category = Java Basics
HistogramQuery.help = Create a histogram from an arbitrary set of objects. The objects are grouped by class, superclass, classloader or package.\n\n\
@@ -135,6 +147,20 @@
LeakHunterQuery.threshold_percent.help = Big memory chunk size in percent of the heap. (Default value is 10%.)
LeakHunterQuery.max_paths.help = Number of paths to garbage collection roots. (Default value is 10000.)
+LeakHunterQuery2.name = Find Leaks between Snapshots
+LeakHunterQuery2.category = Leak Identification
+LeakHunterQuery2.help = Report potential memory leaks.\n\n\
+The query compares the dominator trees from two snapshots and searches for big \
+increases in memory chunks \
+(by default more than 10% of the total baseline heap). These could be single objects \
+or groups of objects from the same class. Then it tries to automatically find the exact \
+accumulation point - usually an array or a collection.
+LeakHunterQuery2.baseline.help = The baseline snapshot file.
+LeakHunterQuery2.options.help = Extra options to use to compare dominator trees.
+LeakHunterQuery2.mask.help = The 'mask' option for the compare.
+LeakHunterQuery2.extraReferences.help = The 'x' option for the compare.
+LeakHunterQuery2.extraReferencesListFile.help = The 'xfile' option for the compare.
+
ObjectListQuery.name = List Objects
ObjectListQuery.menu.0.category = 1|List objects
ObjectListQuery.menu.0.label = 1|with outgoing references
@@ -189,6 +215,17 @@
RetainedSetQuery.fieldNames.help = List of field names
RetainedSetQuery.objects.help = Set of objects to include in the analysis.
+SimpleComparison.name = Simple Comparison
+SimpleComparison.category = Java Basics
+SimpleComparison.help = A comparison of the results of running a query on two snapshots.
+SimpleComparison.baseline.help = The baseline snapshot file.
+SimpleComparison.query.help = The query to run on each snapshot. Can include options.
+SimpleComparison.options.help = The options to pass to the comparison query.
+SimpleComparison.defaultoptions.help = The default options passed to the comparison query. \
+Can be overridden but the default ignores object addresses and array sizes in the comparison.
+SimpleComparison.retained.help = Whether to add approximate or precise retained sizes to the individual queries, \
+ready for the comparison.
+
SoftReferenceStatQuery.name = Soft References Statistics
SoftReferenceStatQuery.category = Java Basics/References
SoftReferenceStatQuery.help = Statistics for Soft References. \
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
index cd57429..f1ebccb 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
@@ -285,6 +285,13 @@
public static String FindLeaksQuery_SearchingSingleObjects;
public static String FindLeaksQuery_TooManySuspects;
+ public static String FindLeaksQuery2_Column_AccPointRetainedHeap;
+ public static String FindLeaksQuery2_Column_SuspectRetainedHeap;
+
+ public static String FindLeaksQuery2_ComparedDominatorTrees;
+
+ public static String FindLeaksQuery2_Leaks;
+
public static String FindStringsQuery_SearchingStrings;
public static String GCRootInfo_BusyMonitor;
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
index cb3e6e2..6edb9e9 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
@@ -258,6 +258,10 @@
FindLeaksQuery_SearchingGroupsOfObjects=Searching suspicious groups of objects ...
FindLeaksQuery_SearchingSingleObjects=Searching suspicious single objects ...
FindLeaksQuery_TooManySuspects=Too many suspect instances ({0}). Will use {1} random from them to search for common path.
+FindLeaksQuery2_ComparedDominatorTrees=Compared Dominator Trees
+FindLeaksQuery2_Leaks=Leaks
+FindLeaksQuery2_Column_AccPointRetainedHeap=Acc. Point Retained Heap Delta
+FindLeaksQuery2_Column_SuspectRetainedHeap=Suspect's Retained Heap Delta
FindStringsQuery_SearchingStrings=Searching Strings...
GCRootInfo_BusyMonitor=Busy Monitor
GCRootInfo_Finalizable=Finalizable
@@ -371,9 +375,9 @@
LeakHunterQuery_Hint=Hint {0}
LeakHunterQuery_Keywords=Keywords
LeakHunterQuery_LeakHunter=Leak Hunter
-LeakHunterQuery_Msg_AccumulatedBy=The memory is accumulated in classloader/component <b>"{0}"</b>.
-LeakHunterQuery_Msg_AccumulatedByInstance=The memory is accumulated in one instance of <b>"{0}"</b> loaded by <b>"{1}"</b>.
-LeakHunterQuery_Msg_AccumulatedByLoadedBy=The memory is accumulated in class <b>"{0}"</b>, loaded by <b>"{1}"</b>.
+LeakHunterQuery_Msg_AccumulatedBy=The memory is accumulated in classloader/component <b>"{0}"</b> which occupies <b>{1}</b> bytes.
+LeakHunterQuery_Msg_AccumulatedByInstance=The memory is accumulated in one instance of <b>"{0}"</b>, loaded by <b>"{1}"</b>, which occupies <b>{2}</b> bytes.
+LeakHunterQuery_Msg_AccumulatedByLoadedBy=The memory is accumulated in class <b>"{0}"</b>, loaded by <b>"{1}"</b>, which occupies <b>{2}</b> bytes.
LeakHunterQuery_Msg_Bytes={0} bytes.
LeakHunterQuery_Msg_Class=The class <b>"{0}"</b>, loaded by <b>"{1}"</b>, occupies <b>{2}</b> bytes.
LeakHunterQuery_Msg_ClassLoader=The classloader/component <b>"{0}"</b> occupies <b>{1}</b> bytes.
@@ -382,9 +386,9 @@
LeakHunterQuery_Msg_ReferencedBy=The instance is referenced by classloader/component. <b>"{0}"</b>.
LeakHunterQuery_Msg_ReferencedByClass=The instance is referenced by class <b>"{0}"</b>, loaded by <b>"{1}"</b>.
LeakHunterQuery_Msg_ReferencedByInstance=The instance is referenced by <b>{0}</b> , loaded by <b>"{1}"</b>.
-LeakHunterQuery_Msg_ReferencedFromClass=These instances are referenced from the class <b>"{0}"</b>, loaded by <b>"{1}"</b>
-LeakHunterQuery_Msg_ReferencedFromClassLoader=These instances are referenced from classloader/component <b>"{0}"</b>
-LeakHunterQuery_Msg_ReferencedFromInstance=These instances are referenced from one instance of <b>"{0}"</b>, loaded by <b>"{1}"</b>
+LeakHunterQuery_Msg_ReferencedFromClass=These instances are referenced from the class <b>"{0}"</b>, loaded by <b>"{1}"</b>, which occupies <b>{2}</b> bytes.
+LeakHunterQuery_Msg_ReferencedFromClassLoader=These instances are referenced from classloader/component <b>"{0}"</b> which occupies <b>{1}</b> bytes.
+LeakHunterQuery_Msg_ReferencedFromInstance=These instances are referenced from one instance of <b>"{0}"</b>, loaded by <b>"{1}"</b>, which occupies <b>{2}</b> bytes.
LeakHunterQuery_Msg_SuspectsRelated=The problem suspects {0} and {1} may be related, because the reference chains to them have a common beginning.
LeakHunterQuery_Msg_Thread=The thread <b>{0}</b> keeps local variables with total size <b>{1}</b> bytes.
LeakHunterQuery_NothingFound=No leak suspect was found
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/snapshot/inspections/CompareTablesQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/snapshot/inspections/CompareTablesQuery.java
index e687d33..8173cf7 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/snapshot/inspections/CompareTablesQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/snapshot/inspections/CompareTablesQuery.java
@@ -62,6 +62,8 @@
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.OQL;
import org.eclipse.mat.snapshot.model.IObject;
+import org.eclipse.mat.snapshot.model.IObjectArray;
+import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
@@ -111,6 +113,9 @@
@Argument(isMandatory = false)
public boolean suffix;
+ // @Argument(isMandatory = false)
+ public boolean addrefs = true;
+
@Argument(isMandatory = false, flag = "x")
public String[] extraReferences;
@@ -247,7 +252,7 @@
IObject obj;
try
{
- obj = snapshots[table].getObject(ctx.getObjectId());
+ obj = snapshots[table].getObject(objId);
}
catch (SnapshotException e)
{
@@ -319,6 +324,92 @@
}
/**
+ * Calculate extra prefix for the key.
+ * Look for the immediate dominator and see if any fields point to this.
+ * Also do this for small object arrays.
+ * @param table index
+ * @param row
+ * @return a String or null
+ */
+ String extraPrefix(int table, Object row)
+ {
+ // Only generate prefixes for small dominator arrays
+ final int SMALL_ARRAY_SIZE = 512;
+ final String noextra = null;
+ if (!addrefs)
+ return noextra;
+ if (snapshots[table] == null)
+ return noextra;
+ IContextObject ctx = tables[table].getContext(row);
+ if (ctx == null)
+ return noextra;
+ int objId = ctx.getObjectId();
+ if (objId == -1)
+ return noextra;
+ IObject obj;
+ try
+ {
+ obj = snapshots[table].getObject(objId);
+ }
+ catch (SnapshotException e)
+ {
+ return noextra;
+ }
+ StringBuilder sb = new StringBuilder();
+ try
+ {
+ int immdom = snapshots[table].getImmediateDominatorId(obj.getObjectId());
+ if (immdom >= 0)
+ {
+ if (!snapshots[table].isArray(immdom))
+ {
+ IObject immobj = snapshots[table].getObject(immdom);
+ for (NamedReference ref : immobj.getOutboundReferences())
+ {
+ if (ref.getObjectId() == objId)
+ {
+ if (sb.length() > 0)
+ sb.append(',');
+ sb.append(ref.getName());
+ }
+ }
+ }
+ else
+ {
+ if (snapshots[table].getClassOf(immdom).getObjectId() == obj.getObjectId())
+ sb.append("<class>"); //$NON-NLS-1$
+ // Big arrays could be expensive to read
+ if (snapshots[table].getHeapSize(immdom) < SMALL_ARRAY_SIZE)
+ {
+ IObject immobj = snapshots[table].getObject(immdom);
+ // Don't get named references for object array - expensive
+ if (immobj instanceof IObjectArray)
+ {
+ IObjectArray immarr = (IObjectArray)immobj;
+ long arr[] = immarr.getReferenceArray();
+ for (int j = 0; j < arr.length; ++j)
+ {
+ if (arr[j] == obj.getObjectAddress())
+ {
+ if (sb.length() > 0)
+ sb.append(',');
+ sb.append('[').append(j).append(']');
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (SnapshotException e1)
+ {
+ }
+ if (sb.length() > 0)
+ return sb.toString();
+ return noextra;
+ }
+
+ /**
* Collect the extra type+field references.
*/
void collectExtraRefs() throws IOException
@@ -481,12 +572,12 @@
rows = new Object[tables.length];
map.put(key, rows);
}
- int ii = i;
+ int ii = 0;
if (lastcache.containsKey(key))
{
ii = lastcache.get(key);
}
- while (rows[ii] != null)
+ while (rows[ii + i] != null)
{
/*
* Normally:
@@ -500,17 +591,21 @@
if (ii >= rows.length)
{
sortwork -= rows.length * rows.length;
- // Add a placeholder so that a row goes here
- map.put(new PlaceHolder(key, rows.length), new Object[0]);
// Grow the row array
- rows = Arrays.copyOf(rows, rows.length + (rows.length / tables.length / 2 + 1) * tables.length);
+ int spare = rows.length / tables.length / 2;
+ rows = Arrays.copyOf(rows, rows.length + (spare + 1) * tables.length);
map.put(key, rows);
sortwork += rows.length * rows.length;
}
+ }
+ rows[ii + i] = row;
+ if (ii > 0)
+ {
// With many duplicates it can take a long time to find a free slot, so cache the last used
lastcache.put(key, ii);
+ // Add a placeholder so that a row goes here
+ map.put(new PlaceHolder(key, ii), new Object[0]);
}
- rows[ii] = row;
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
listener.worked(1);
@@ -592,25 +687,39 @@
{
Column c = tables[i].getColumns()[keyColumn - 1];
IDecorator id = c.getDecorator();
- if (id != null)
+ String pfx;
+ if (prefix)
{
- String pfx = prefix ? id.prefix(row) : null;
- String sfx = suffix ? id.suffix(row) : null;
- if (pfx != null)
+ if (id != null)
{
- pfx = pfx.replaceAll(mask.pattern(), replace == null ? "" : replace); //$NON-NLS-1$
- if (pfx.length() == 0)
- pfx = null;
+ pfx = id.prefix(row);
+ if (pfx == null)
+ pfx = extraPrefix(i, row);
}
- if (sfx != null)
+ else
{
- sfx = mask.matcher(sfx).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
- if (sfx.length() == 0)
- sfx = null;
+ pfx = extraPrefix(i, row);
}
- if (pfx != null || sfx != null)
- key = new ComparedRow(pfx, key, sfx, null);
}
+ else
+ {
+ pfx = null;
+ }
+ String sfx = suffix && id != null ? id.suffix(row) : null;
+ if (mask != null && pfx != null)
+ {
+ pfx = mask.matcher(pfx).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
+ if (pfx.length() == 0)
+ pfx = null;
+ }
+ if (mask != null && sfx != null)
+ {
+ sfx = mask.matcher(sfx).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
+ if (sfx.length() == 0)
+ sfx = null;
+ }
+ if (pfx != null || sfx != null)
+ key = new ComparedRow(pfx, key, sfx, null);
}
return key;
}
@@ -1945,6 +2054,9 @@
return DeltaEncoding.GE;
else if (fv.startsWith(Messages.CompareTablesQuery_GE))
return DeltaEncoding.GE;
+ else if (fv.startsWith("<= ")) //$NON-NLS-1$
+ // E.g. Quantize_LessEq_Prefix
+ return DeltaEncoding.LE;
else if (fv.startsWith(Messages.CompareTablesQuery_LE))
return DeltaEncoding.LE;
else if (fv.startsWith(Messages.CompareTablesQuery_APPROX))
@@ -2026,6 +2138,13 @@
return encodeResult(ret, returnBytes, approxValue, approxPreviousValue, columnIdx);
}
}
+ else
+ {
+ if (ratio)
+ return null;
+ if (previousTableValue == null || !previousTableValue.equals(value))
+ return value;
+ }
return null;
}
diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/SnapshotImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/SnapshotImpl.java
index 1a178a5..fd7fc4d 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/SnapshotImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/SnapshotImpl.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2018 SAP AG, IBM Corporation and others.
+ * Copyright (c) 2008, 2020 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 v1.0
* which accompanies this distribution, and is available at
@@ -17,10 +17,10 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InvalidClassException;
+import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
-import java.io.ObjectStreamClass;
+import java.io.ObjectStreamClass;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -1600,6 +1600,17 @@
throw new RuntimeException(error);
}
+ /**
+ * Tidy up and close files so indices could be deleted if required.
+ * Normally is disposed, but secondary snapshots are not.
+ * Shouldn't really be needed as files should have finalizers,
+ * but deleting indices does not work.
+ */
+ public void finalize()
+ {
+ dispose();
+ }
+
// //////////////////////////////////////////////////////////////
// internal stuff
// //////////////////////////////////////////////////////////////
diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/OQLQueryImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/OQLQueryImpl.java
index a88d73b..981c32a 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/OQLQueryImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/OQLQueryImpl.java
@@ -253,8 +253,13 @@
public RowMap get(int index)
{
// Always return a map not a single item for consistency.
- //if (false && getColumns().length == 1)
- // return getColumnValue(getRow(index), 0);
+ // Except if the alias is "" as this used for CompareTablesQuery.
+ if (getColumns().length == 1 && getColumns()[0].getLabel().length() == 0)
+ {
+ Object ret = getColumnValue(getRow(index), 0);
+ if (ret instanceof RowMap)
+ return (RowMap)ret;
+ }
// Delay resolving columns until needed
return new RowMap(this, index);
}
diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueriesTest.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueriesTest.java
index d3b1fe3..ee278e6 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueriesTest.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueriesTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011, 2019 IBM Corporation.
+ * Copyright (c) 2011, 2020 IBM Corporation.
* 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
@@ -13,8 +13,10 @@
import static org.hamcrest.core.AllOf.allOf;
import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.hamcrest.number.OrderingComparison.greaterThan;
import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
import static org.hamcrest.number.OrderingComparison.lessThan;
@@ -45,6 +47,12 @@
import org.eclipse.mat.query.refined.RefinedResultBuilder;
import org.eclipse.mat.query.refined.RefinedTable;
import org.eclipse.mat.query.refined.RefinedTree;
+import org.eclipse.mat.query.results.CompositeResult;
+import org.eclipse.mat.query.results.DisplayFileResult;
+import org.eclipse.mat.query.results.TextResult;
+import org.eclipse.mat.report.QuerySpec;
+import org.eclipse.mat.report.SectionSpec;
+import org.eclipse.mat.report.Spec;
import org.eclipse.mat.snapshot.ClassHistogramRecord;
import org.eclipse.mat.snapshot.Histogram;
import org.eclipse.mat.snapshot.ISnapshot;
@@ -1174,5 +1182,162 @@
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:compare -params snapshot2="+snapshot2.getSnapshotInfo().getPath(), snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ if (t instanceof DisplayFileResult)
+ {
+ // If an error occurred the file might be short
+ assertThat(((DisplayFileResult) t).getFile().length(), greaterThan(2000L));
+ }
+ }
+
+ /**
+ * Test running the overview compare snapshots report defined in a plugin, with parameters.
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testOverview2Report() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK6_18_64BIT, false); // Do not dispose this as shared
+ SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:overview2 -params baseline="+snapshot.getSnapshotInfo().getPath(), snapshot2);
+ IResult t = query.execute(new VoidProgressListener());
+ assertNotNull(t);
+ if (t instanceof DisplayFileResult)
+ {
+ // If an error occurred the file might be short
+ assertThat(((DisplayFileResult) t).getFile().length(), greaterThan(2000L));
+ }
+ }
+
+ /**
+ * Test running the snapshot compare snapshots report defined in a plugin, with parameters.
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testSuspects2Report() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK6_18_64BIT, false); // Do not dispose this as shared
+ SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:suspects2 -params baseline="+snapshot.getSnapshotInfo().getPath(), snapshot2);
+ IResult t = query.execute(new VoidProgressListener());
+ assertNotNull(t);
+ }
+
+ /**
+ * Comparison of two JDK6 dumps
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testLeakHunter2ReportJDK6() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK6_18_64BIT, false); // Do not dispose this as shared
+ testLeakHunter2Report(snapshot, snapshot2, 4, 1, 12);
+ }
+
+ /**
+ * Comparison of dump with itself - should not be a leak
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testLeakHunter2ReportJDK6none() throws SnapshotException, IOException
+ {
+ testLeakHunter2Report(snapshot, snapshot, 0, 0, 0);
+ }
+
+ /**
+ * Comparison of two JDK11 dumps
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testLeakHunter2ReportIBM8_7() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot1 = TestSnapshots.getSnapshot(TestSnapshots.IBM_JDK7_64BIT_SYSTEM, false); // Do not dispose this as shared
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.IBM_JDK8_64BIT_SYSTEM, false); // Do not dispose this as shared
+ testLeakHunter2Report(snapshot1, snapshot2, 6, 1, 18);
+ }
+
+ /**
+ * Comparison of two JDK11 dumps
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testLeakHunter2ReportJDK11() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot1 = TestSnapshots.getSnapshot(TestSnapshots.ADOPTOPENJDK_HOTSPOT_JDK11_0_4_11_64BIT, false); // Do not dispose this as shared
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.OPENJDK_JDK11_04_64BIT, false); // Do not dispose this as shared
+ testLeakHunter2Report(snapshot1, snapshot2, 9, 1, 22);
+ }
+
+ /**
+ * Reverse comparison - should not be a leak
+ * @throws SnapshotException
+ * @throws IOException
+ */
+ @Test
+ public void testLeakHunter2ReportJDK11none() throws SnapshotException, IOException
+ {
+ ISnapshot snapshot1 = TestSnapshots.getSnapshot(TestSnapshots.ADOPTOPENJDK_HOTSPOT_JDK11_0_4_11_64BIT, false); // Do not dispose this as shared
+ ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.OPENJDK_JDK11_04_64BIT, false); // Do not dispose this as shared
+ testLeakHunter2Report(snapshot2, snapshot1, 0, 0, 0);
+ }
+
+ public void testLeakHunter2Report(ISnapshot snapshot1, ISnapshot snapshot2, int expectedProbs, int expectedDomTree, int expectedSubCommands) throws SnapshotException, IOException
+ {
+ SnapshotQuery query = SnapshotQuery.parse("leakhunter2 -baseline "+snapshot1.getSnapshotInfo().getPath(), snapshot2);
+ IResult t = query.execute(new VoidProgressListener());
+ assertNotNull(t);
+ if (expectedProbs == 0)
+ {
+ assertThat(t, instanceOf(TextResult.class));
+ return;
+ }
+ // Should be 4 suspects
+ SectionSpec ss = (SectionSpec)t;
+ int probs = 0;
+ int domtree = 0;
+ int subcommands = 0;
+ for (Spec s : ss.getChildren())
+ {
+ if (s.getName().contains("Problem"))
+ ++probs;
+ if (s instanceof QuerySpec)
+ {
+ // This is the find_leaks2 query
+ QuerySpec qs = (QuerySpec)s;
+ if (qs.getName().equals("Compared Dominator Trees"))
+ {
+ assertThat(qs.getCommand(), startsWith("find_leaks2 "));
+ SnapshotQuery query2 = SnapshotQuery.parse(qs.getCommand(), snapshot2);
+ IResult t2 = query2.execute(new VoidProgressListener());
+ assertNotNull(t2);
+ ++domtree;
+ }
+ IResult t3 = qs.getResult();
+ if (t3 instanceof CompositeResult)
+ {
+ CompositeResult cr = (CompositeResult)t3;
+ for (CompositeResult.Entry e : cr.getResultEntries())
+ {
+ if (e.getResult() instanceof QuerySpec)
+ {
+ QuerySpec qs2 = (QuerySpec)e.getResult();
+ if (qs2.getCommand() != null)
+ {
+ SnapshotQuery query2 = SnapshotQuery.parse(qs2.getCommand(), snapshot2);
+ IResult t2 = query2.execute(new VoidProgressListener());
+ assertNotNull(t2);
+ ++subcommands;
+ }
+ }
+ }
+ }
+ }
+ }
+ assertThat("Expect problems section", probs, equalTo(expectedProbs));
+ assertThat("Expected dominator tree section", domtree, equalTo(expectedDomTree));
+ assertThat("Expected subqueries with commands", subcommands, equalTo(expectedSubCommands));
}
}
diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueryLookupTest.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueryLookupTest.java
index 8705415..f7e3d8b 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueryLookupTest.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/QueryLookupTest.java
@@ -482,7 +482,7 @@
public void testCompareDiffRatioPreviousRetained(String query) throws SnapshotException, ParseException
{
- boolean verbose = true;
+ boolean verbose = false;
String queryId = "comparetablesquery";
ISnapshot snapshot1 = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK6_18_64BIT, false);
ISnapshot snapshot2 = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK6_18_32BIT, false);