blob: 4cdc762c3956dae73a45b8e33b0b2162f33ad081 [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
* Andrew Johnson/IBM Corporation - OQL continuation
*******************************************************************************/
package org.eclipse.mat.inspections.component;
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.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.internal.Messages;
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.CommandName;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.report.Params;
import org.eclipse.mat.report.QuerySpec;
import org.eclipse.mat.report.SectionSpec;
import org.eclipse.mat.snapshot.ISnapshot;
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 org.eclipse.mat.util.SimpleMonitor;
@CommandName("component_report_top")
@Icon("/META-INF/icons/top_components_report.gif")
public class TopComponentsReportQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument(isMandatory = false, flag = "t")
public int thresholdPercent = 1;
@Argument(isMandatory = false)
public boolean aggressive;
public IResult execute(IProgressListener listener) throws Exception
{
SimpleMonitor sm = new SimpleMonitor(Messages.TopComponentsReportQuery_TopComponentReports, listener, new int[] {10,90});
int[] topDominators = snapshot.getImmediateDominatedIds(-1);
List<Record> loaders = createClassLoaderRecords(sm.nextMonitor(), topDominators);
SectionSpec result = new SectionSpec(Messages.TopComponentsReportQuery_TopComponentReports);
long totalHeapSize = snapshot.getSnapshotInfo().getUsedHeapSize();
long threshold = totalHeapSize / 100 * thresholdPercent;
int nr = 0;
for (Record record : loaders)
{
if (record.retainedSize < threshold)
break;
++nr;
}
int tasks[] = new int[nr];
int nobj = snapshot.getSnapshotInfo().getNumberOfObjects();
long totalheap = snapshot.getSnapshotInfo().getUsedHeapSize();
if (totalheap > 0)
{
for (int i = 0; i < nr; ++i)
{
// estimate how much work for each loader
tasks[i] = (int)(loaders.get(i).retainedSize * nobj / totalheap);
}
}
else
{
Arrays.fill(tasks, 100);
}
SimpleMonitor sm2 = new SimpleMonitor(Messages.TopComponentsReportQuery_TopComponentReports, sm.nextMonitor(), tasks);
for (Record record : loaders)
{
if (listener.isCanceled())
break;
if (record.retainedSize < threshold)
break;
String oql = oqlLoader(record.name, record.loaderAddr);
oql = oql.replace("\"", "\\\""); //$NON-NLS-1$ //$NON-NLS-2$
SnapshotQuery query = SnapshotQuery.parse("component_report "+oql+";", snapshot); //$NON-NLS-1$ //$NON-NLS-2$
query.setArgument("aggressive", aggressive); //$NON-NLS-1$
IResult report = query.execute(sm2.nextMonitor());
QuerySpec spec = new QuerySpec(MessageUtil.format("{0} ({1,number,percent})", //$NON-NLS-1$
record.name, (double) record.retainedSize / (double) totalHeapSize), report);
spec.set(Params.Html.SEPARATE_FILE, Boolean.TRUE.toString());
/*
* Set a command which generates the same report for a class loader
* Uses OQL to select from the top level dominators,
* choosing an object which is the class loader,
* or is a class and is loaded by the loader
* or is an ordinary object of type of of a class loaded by the class loader.
*/
spec.setCommand("component_report "+ //$NON-NLS-1$
(aggressive ? "-aggressive " : "") + //$NON-NLS-1$//$NON-NLS-2$
oql + ";" ); //$NON-NLS-1$
result.add(spec);
}
return result;
}
private String oqlLoader(String name, long loaderAddress)
{
return
//"select * from objects (select objects a from objects (dominators(-1)) a) b"+ //$NON-NLS-1$
// Add the component name as a comment
"select * /* " + name.replace("*/", "* /") + " */ from objects (select objects a from objects (dominators(-1)) a) b"+ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
" where"+ //$NON-NLS-1$
" b implements org.eclipse.mat.snapshot.model.IClassLoader and b.@objectAddress = " + loaderAddress + "L or"+ //$NON-NLS-1$ //$NON-NLS-2$
" b implements org.eclipse.mat.snapshot.model.IClass and b.@classLoaderAddress = " + loaderAddress + "L or"+ //$NON-NLS-1$ //$NON-NLS-2$
" b implements org.eclipse.mat.snapshot.model.IClassLoader = false and b implements org.eclipse.mat.snapshot.model.IClass = false and b.@clazz.@classLoaderAddress = " + loaderAddress + "L"; //$NON-NLS-1$ //$NON-NLS-2$
//" $ {snapshot}.isClass(b.@objectId) and b.@classLoaderAddress = " + loaderAddress + "L or"+ //$NON-NLS-1$ //$NON-NLS-2$
//" $ {snapshot}.isClassLoader(b.@objectId) = false and $ {snapshot}.isClass(b.@objectId) = false and b.@clazz.@classLoaderAddress = " + loaderAddress + "L"; //$NON-NLS-1$ //$NON-NLS-2$
}
private List<Record> createClassLoaderRecords(IProgressListener listener, int[] topDominators)
throws SnapshotException
{
HashMapIntObject<Record> id2loader = new HashMapIntObject<Record>();
for (int ii = 0; ii < topDominators.length; ii++)
{
int classLoaderId;
if (snapshot.isClass(topDominators[ii]))
{
classLoaderId = ((IClass) snapshot.getObject(topDominators[ii])).getClassLoaderId();
}
else if (snapshot.isClassLoader(topDominators[ii]))
{
classLoaderId = topDominators[ii];
}
else
{
classLoaderId = snapshot.getClassOf(topDominators[ii]).getClassLoaderId();
}
Record loaderRecord = id2loader.get(classLoaderId);
if (loaderRecord == null)
{
IObject loader = snapshot.getObject(classLoaderId);
String name = loader.getClassSpecificName();
if (name == null)
name = loader.getTechnicalName();
loaderRecord = new Record(name, loader.getObjectAddress());
id2loader.put(classLoaderId, loaderRecord);
}
loaderRecord.objects.add(topDominators[ii]);
loaderRecord.retainedSize += snapshot.getRetainedHeapSize(topDominators[ii]);
if (ii % 1000 == 0)
{
listener.worked(1);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
}
List<Record> loaders = new ArrayList<Record>(id2loader.size());
for (Iterator<Record> ee = id2loader.values(); ee.hasNext();)
loaders.add(ee.next());
Collections.sort(loaders);
return loaders;
}
// //////////////////////////////////////////////////////////////
// internal classes
// //////////////////////////////////////////////////////////////
/**
* Records objects associated with this class loader.
*/
private static class Record implements Comparable<Record>
{
@Override
public int hashCode()
{
return Objects.hash(loaderAddr, name, retainedSize);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Record other = (Record) obj;
return loaderAddr == other.loaderAddr && Objects.equals(name, other.name)
&& retainedSize == other.retainedSize;
}
String name;
ArrayInt objects = new ArrayInt();
long retainedSize;
long loaderAddr;
/**
* Create record to hold objects associated with the class loader
* @param name The name of the class loader
* @param addr Its address
*/
public Record(String name, long addr)
{
this.name = name;
this.loaderAddr = addr;
}
public int compareTo(Record other)
{
if (retainedSize > other.retainedSize)
return -1;
if (retainedSize < other.retainedSize)
return 1;
int r = name.compareTo(other.name);
if (r != 0)
return r;
return Long.compare(loaderAddr, other.loaderAddr);
}
}
}