blob: ce532c4f0e334ae021dcec856820317d900f99a2 [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 - enhancements and fixes
* James Livingston - expose collection utils as API
*******************************************************************************/
package org.eclipse.mat.inspections.collections;
import java.util.Arrays;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.HashMapIntLong;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.inspections.collectionextract.CollectionExtractionUtils;
import org.eclipse.mat.inspections.collectionextract.ExtractedMap;
import org.eclipse.mat.inspections.collectionextract.IMapExtractor;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.internal.collectionextract.HashMapCollectionExtractor;
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.HelpUrl;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.query.quantize.Quantize;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.extension.Subjects;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.query.IHeapObjectArgument;
import org.eclipse.mat.snapshot.query.RetainedSizeDerivedData;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
@CommandName("map_collision_ratio")
@Icon("/META-INF/icons/map_collision.gif")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/analyzingjavacollectionusage.html")
@Subjects({"java.util.AbstractMap",
"java.util.jar.Attributes",
"java.util.Dictionary",
"java.lang.ThreadLocal$ThreadLocalMap",
"java.util.concurrent.ConcurrentHashMap$Segment",
"java.util.concurrent.ConcurrentHashMap$CollectionView",
"java.util.Collections$SynchronizedMap",
"java.util.Collections$UnmodifiableMap",
"java.util.Collections$CheckedMap",
"java.util.Collections$EmptyMap",
"java.util.ResourceBundle",
"java.awt.RenderingHints",
"sun.awt.WeakIdentityHashMap",
"javax.script.SimpleBindings",
"javax.management.openmbean.TabularDataSupport",
"com.ibm.jvm.util.HashMapRT",
"com.sap.engine.lib.util.AbstractDataStructure",
"org.eclipse.mat.collect.HashMapIntLong",
"org.eclipse.mat.collect.HashMapIntObject",
"org.eclipse.mat.collect.HashMapLongObject",
"org.eclipse.mat.collect.HashMapObjectLong",
// Sometimes useful to see collision ratio of a HashSet
"java.util.HashSet",
"java.util.Collections$SetFromMap",
"java.util.Properties$EntrySet",
"java.util.Collections$EmptySet",
})
public class MapCollisionRatioQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument(flag = Argument.UNFLAGGED)
public IHeapObjectArgument objects;
@Argument(isMandatory = false)
public int segments = 5;
@Argument(isMandatory = false)
public String collection;
@Argument(isMandatory = false)
public String size_attribute;
@Argument(isMandatory = false)
public String array_attribute;
private static class Result
{
final double ratio;
final long used;
public Result(double ratio, long used)
{
this.ratio = ratio;
this.used = used;
}
}
public IResult execute(IProgressListener listener) throws Exception
{
listener.subTask(Messages.MapCollisionRatioQuery_CalculatingCollisionRatios);
// create frequency distribution
Quantize.Builder builder = Quantize.linearFrequencyDistribution(
Messages.MapCollisionRatioQuery_Column_CollisionRatio, 0, 1, (double) 1 / (double) segments);
builder.column(Messages.MapCollisionRatioQuery_Column_NumObjects, Quantize.COUNT);
builder.column(Messages.Column_ShallowHeap, Quantize.SUM_BYTES);
builder.addDerivedData(RetainedSizeDerivedData.APPROXIMATE);
Quantize quantize = builder.build();
IMapExtractor specificExtractor = new HashMapCollectionExtractor(size_attribute, array_attribute, null, null);
final long LIMIT = 20;
HashMapIntLong exceptions = new HashMapIntLong();
int counter = 0;
IClass type = null;
for (int[] objectIds : objects)
{
HashMapIntObject<Result> resultMap = null;
int sortedObjs[] = objectIds;
int prev = Integer.MIN_VALUE;
for (int objectId : objectIds)
{
if (objectId < prev)
{
sortedObjs = objectIds.clone();
Arrays.sort(sortedObjs);
resultMap = new HashMapIntObject<Result>();
break;
}
prev = objectId;
}
for (int objectId : sortedObjs)
{
if (listener.isCanceled())
break;
IObject obj = snapshot.getObject(objectId);
if (counter++ % 1000 == 0 && !obj.getClazz().equals(type))
{
type = obj.getClazz();
listener.subTask(Messages.MapCollisionRatioQuery_CalculatingCollisionRatios + "\n" + type.getName()); //$NON-NLS-1$
}
try
{
ExtractedMap coll = CollectionExtractionUtils.extractMap(obj, collection, specificExtractor);
if (coll != null)
{
/*
* @FIXME - shouldn't really count maps without a collision ratio
* but current tests presume TreeSet/TreeMap have one.
*/
if (coll.hasCollisionRatio() || true)
{
Double collisionRatio = coll.getCollisionRatio();
if (collisionRatio == null)
collisionRatio = 0.0;
if (resultMap != null)
resultMap.put(objectId, new Result(collisionRatio, coll.getUsedHeapSize()));
else
quantize.addValue(objectId, collisionRatio, null, coll.getUsedHeapSize());
}
}
}
catch (RuntimeException | SnapshotException e)
{
int classId = obj.getClazz().getObjectId();
if (!exceptions.containsKey(classId))
{
exceptions.put(classId, 0);
}
long c = exceptions.get(classId);
exceptions.put(classId, c + 1);
if (c < LIMIT)
{
listener.sendUserMessage(
IProgressListener.Severity.INFO,
MessageUtil.format(Messages.MapCollisionRatioQuery_IgnoringCollection,
obj.getTechnicalName()), e);
}
}
}
if (resultMap != null)
{
for (int objectId : objectIds)
{
if (resultMap.containsKey(objectId))
{
Result r = resultMap.get(objectId);
quantize.addValue(objectId, r.ratio, null, r.used);
}
}
}
if (listener.isCanceled())
break;
}
return quantize.getResult();
}
}