blob: 5814adbc8ca3c046bbeb13b4fbc567ba27d51a7e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP AG - initial API and implementation
* IBM Corporation - add OQL
*******************************************************************************/
package org.eclipse.mat.inspections;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.HashMapIntObject.Entry;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.Column.SortDirection;
import org.eclipse.mat.query.Bytes;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IContextObjectSet;
import org.eclipse.mat.query.IDecorator;
import org.eclipse.mat.query.IIconProvider;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.ResultMetaData;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.CommandName;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.snapshot.query.ObjectListResult;
import org.eclipse.mat.util.IProgressListener;
@CommandName("gc_roots")
@Icon("/META-INF/icons/roots.gif")
@HelpUrl("/org.eclipse.mat.ui.help/concepts/gcroots.html")
public class GCRootsQuery implements IQuery
{
private static final URL ICON = Icons.getURL("roots.gif"); //$NON-NLS-1$
@Argument
public ISnapshot snapshot;
public IResult execute(IProgressListener listener) throws Exception
{
int[] roots = snapshot.getGCRoots();
HashMapIntObject<HashMapIntObject<ArrayInt>> rootsByType = new HashMapIntObject<HashMapIntObject<ArrayInt>>();
for (int root : roots)
{
GCRootInfo[] info = snapshot.getGCRootInfo(root);
int classId = snapshot.getClassOf(root).getObjectId();
for (GCRootInfo rootInfo : info)
{
HashMapIntObject<ArrayInt> type = rootsByType.get(rootInfo.getType());
if (type == null)
rootsByType.put(rootInfo.getType(), type = new HashMapIntObject<ArrayInt>());
ArrayInt byClass = type.get(classId);
if (byClass == null)
type.put(classId, byClass = new ArrayInt());
byClass.add(root);
}
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
List<GCType> types = new ArrayList<GCType>();
for (Iterator<Entry<HashMapIntObject<ArrayInt>>> iter = rootsByType.entries(); iter.hasNext();)
{
Entry<HashMapIntObject<ArrayInt>> entry = iter.next();
GCType type = new GCType(GCRootInfo.getTypeAsString(entry.getKey()), entry.getKey());
types.add(type);
for (Iterator<Entry<ArrayInt>> iterObjects = entry.getValue().entries(); iterObjects.hasNext();)
{
Entry<ArrayInt> entryObjects = iterObjects.next();
ClassRecord record = new ClassRecord((IClass) snapshot.getObject(entryObjects.getKey()), //
entryObjects.getValue().toArray());
type.records.add(record);
type.count += record.objectIds.length;
}
Collections.sort(type.records);
}
Collections.sort(types);
return new Result(snapshot, roots, types);
}
// //////////////////////////////////////////////////////////////
// internal classes
// //////////////////////////////////////////////////////////////
private static class GCType implements Comparable<GCType>
{
@Override
public int hashCode()
{
return Objects.hash(count, name);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GCType other = (GCType) obj;
return count == other.count && Objects.equals(name, other.name);
}
String name;
int count;
int type;
List<ClassRecord> records = new ArrayList<ClassRecord>();
public GCType(String name, int type)
{
this.name = name;
this.type = type;
}
public int compareTo(GCType other)
{
return count > other.count ? -1 : count < other.count ? 1
: name.compareTo(other.name);
}
}
private static class ClassRecord implements Comparable<ClassRecord>
{
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(objectIds);
result = prime * result + Objects.hash(classId);
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClassRecord other = (ClassRecord) obj;
return classId == other.classId && Arrays.equals(objectIds, other.objectIds);
}
final int classId;
final String name;
final int[] objectIds;
public ClassRecord(IClass object, int[] objectIds)
{
this.classId = object.getObjectId();
this.name = object.getName();
this.objectIds = objectIds;
}
public int compareTo(ClassRecord o)
{
return objectIds.length > o.objectIds.length ? -1 : objectIds.length < o.objectIds.length ? 1
: name.compareTo(o.name);
}
}
private static class Result implements IResultTree, IIconProvider, IDecorator
{
private List<GCType> rootTypes;
private HashMapIntObject<Object> root2element;
private ObjectListResult.Outbound objectList;
public Result(ISnapshot snapshot, int[] roots, List<GCType> rootTypes)
{
this.rootTypes = rootTypes;
root2element = new HashMapIntObject<Object>();
objectList = new ObjectListResult.Outbound(snapshot, roots);
for (Object o : objectList.getElements())
root2element.put(objectList.getContext(o).getObjectId(), o);
}
public ResultMetaData getResultMetaData()
{
return new ResultMetaData.Builder() //
.setIsPreSortedBy(1, SortDirection.DESC) //
.build();
}
public Column[] getColumns()
{
return new Column[] { new Column(Messages.Column_ClassName).decorator(this), //
new Column(Messages.Column_Objects, int.class), //
new Column(Messages.Column_ShallowHeap, Bytes.class).noTotals(), //
new Column(Messages.Column_RetainedHeap, Bytes.class).noTotals() };
}
public List<?> getElements()
{
return rootTypes;
}
public boolean hasChildren(Object element)
{
if (element instanceof GCType)
return true;
if (element instanceof ClassRecord)
return true;
return objectList.hasChildren(element);
}
public List<?> getChildren(Object parent)
{
if (parent instanceof GCType)
return ((GCType) parent).records;
if (parent instanceof ClassRecord)
return asList(((ClassRecord) parent).objectIds);
return objectList.getChildren(parent);
}
private List<?> asList(int[] objectIds)
{
List<Object> answer = new ArrayList<Object>(objectIds.length);
for (int id : objectIds)
answer.add(root2element.get(id));
return answer;
}
public Object getColumnValue(Object row, int columnIndex)
{
if (row instanceof GCType)
{
GCType type = (GCType) row;
switch (columnIndex)
{
case 0:
return type.name;
case 1:
return type.count;
default:
return null;
}
}
else if (row instanceof ClassRecord)
{
ClassRecord record = (ClassRecord) row;
switch (columnIndex)
{
case 0:
return record.name;
case 1:
return record.objectIds.length;
default:
return null;
}
}
else
{
switch (columnIndex)
{
case 0:
return objectList.getColumnValue(row, columnIndex);
case 1:
return null;
default:
return objectList.getColumnValue(row, columnIndex - 1);
}
}
}
public IContextObject getContext(final Object row)
{
if (row instanceof GCType)
{
return new IContextObjectSet()
{
public int getObjectId()
{
return -1;
}
public int[] getObjectIds()
{
ArrayInt roots = new ArrayInt();
for (ClassRecord record : ((GCType) row).records)
roots.addAll(record.objectIds);
return roots.toArray();
}
public String getOQL()
{
return "SELECT OBJECTS r FROM OBJECTS ${snapshot}.@GCRoots r WHERE (SELECT s FROM OBJECTS ${snapshot}.getGCRootInfo(r) s WHERE s.@type = "+((GCType) row).type+") != null"; //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
else if (row instanceof ClassRecord)
{
return new IContextObjectSet()
{
public int getObjectId()
{
return ((ClassRecord) row).classId;
}
public int[] getObjectIds()
{
return ((ClassRecord) row).objectIds;
}
public String getOQL()
{
return null;
}
};
}
else
{
return objectList.getContext(row);
}
}
public String prefix(Object row)
{
if (row instanceof GCType)
return null;
else if (row instanceof ClassRecord)
return null;
else
return objectList.prefix(row);
}
public String suffix(Object row)
{
if (row instanceof GCType)
return null;
else if (row instanceof ClassRecord)
return null;
else
return objectList.suffix(row);
}
public URL getIcon(Object row)
{
if (row instanceof GCType)
return ICON;
else if (row instanceof ClassRecord)
return Icons.CLASS;
else
return objectList.getIcon(row);
}
}
}