Refactor ahat's perflib api.
This change substantially refactors how ahat accesses heap dump data.
Rather than use the perflib API directly with some additional
information accessed on the side via AhatSnapshot, we introduce an
entirely new API for accessing all the information we need from a heap
dump. Perflib is used when processing the heap dump to populate the
information initially, but afterwards all views and handlers go
through the new com.android.ahat.heapdump API.
The primary motivation for this change is to facilitate adding support
for diffing two heap dumps to ahat. The new API provides flexibility
that will make it easier to form links between objects in different
snapshots and introduce place holder objects to show when there is an
object in another snapshot that has no corresponding object in this
snapshot.
A large number of test cases were added to cover missing cases
discovered in the process of refactoring ahat's perflib API.
The external user-facing UI may have minor cosmetic changes, but
otherwise is unchanged.
Test: m ahat-test, with many new tests added.
Bug: 33770653
Change-Id: I1a6b05ea469ebbbac67d99129dd9faa457b4d17e
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 493eafb..8859c68 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -98,7 +98,7 @@
ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
ahat-test: PRIVATE_AHAT_PROGUARD_MAP := $(AHAT_TEST_DUMP_PROGUARD_MAP)
ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF)
- java -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
+ java -enableassertions -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
# Clean up local variables.
AHAT_TEST_DUMP_DEPENDENCIES :=
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
deleted file mode 100644
index ba8243f..0000000
--- a/tools/ahat/src/AhatSnapshot.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.RootType;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.StackFrame;
-import com.android.tools.perflib.heap.StackTrace;
-
-import com.google.common.collect.Lists;
-
-import gnu.trove.TObjectProcedure;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper over the perflib snapshot that provides the behavior we use in
- * ahat.
- */
-class AhatSnapshot {
- private final Snapshot mSnapshot;
- private final List<Heap> mHeaps;
-
- // Map from Instance to the list of Instances it immediately dominates.
- private final Map<Instance, List<Instance>> mDominated
- = new HashMap<Instance, List<Instance>>();
-
- // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
- private final List<Instance> mRooted = new ArrayList<Instance>();
-
- // Map from roots to their types.
- // Instances are only included if they are roots, and the collection of root
- // types is guaranteed to be non-empty.
- private final Map<Instance, Collection<RootType>> mRoots
- = new HashMap<Instance, Collection<RootType>>();
-
- private final Site mRootSite = new Site("ROOT");
- private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>();
-
- private final List<InstanceUtils.NativeAllocation> mNativeAllocations
- = new ArrayList<InstanceUtils.NativeAllocation>();
-
- /**
- * Create an AhatSnapshot from an hprof file.
- */
- public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
- Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprof), map);
- snapshot.computeDominators();
- return new AhatSnapshot(snapshot);
- }
-
- /**
- * Construct an AhatSnapshot for the given perflib snapshot.
- * Ther user is responsible for calling snapshot.computeDominators before
- * calling this AhatSnapshot constructor.
- */
- private AhatSnapshot(Snapshot snapshot) {
- mSnapshot = snapshot;
- mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
-
- final ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
- for (Heap heap : mHeaps) {
- // Use a single element array for the total to act as a reference to a
- // long.
- final long[] total = new long[]{0};
- TObjectProcedure<Instance> processInstance = new TObjectProcedure<Instance>() {
- @Override
- public boolean execute(Instance inst) {
- Instance dominator = inst.getImmediateDominator();
- if (dominator != null) {
- total[0] += inst.getSize();
-
- if (dominator == Snapshot.SENTINEL_ROOT) {
- mRooted.add(inst);
- }
-
- // Properly label the class of a class object.
- if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) {
- inst.setClassId(javaLangClass.getId());
- }
-
- // Update dominated instances.
- List<Instance> instances = mDominated.get(dominator);
- if (instances == null) {
- instances = new ArrayList<Instance>();
- mDominated.put(dominator, instances);
- }
- instances.add(inst);
-
- // Update sites.
- List<StackFrame> path = Collections.emptyList();
- StackTrace stack = getStack(inst);
- int stackId = getStackTraceSerialNumber(stack);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null && frames.length > 0) {
- path = Lists.reverse(Arrays.asList(frames));
- }
- }
- mRootSite.add(stackId, 0, path.iterator(), inst);
-
- // Update native allocations.
- InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst);
- if (alloc != null) {
- mNativeAllocations.add(alloc);
- }
- }
- return true;
- }
- };
- for (Instance instance : heap.getClasses()) {
- processInstance.execute(instance);
- }
- heap.forEachInstance(processInstance);
- mHeapSizes.put(heap, total[0]);
- }
-
- // Record the roots and their types.
- for (RootObj root : snapshot.getGCRoots()) {
- Instance inst = root.getReferredInstance();
- Collection<RootType> types = mRoots.get(inst);
- if (types == null) {
- types = new HashSet<RootType>();
- mRoots.put(inst, types);
- }
- types.add(root.getRootType());
- }
- }
-
- // Note: This method is exposed for testing purposes.
- public ClassObj findClass(String name) {
- return mSnapshot.findClass(name);
- }
-
- public Instance findInstance(long id) {
- return mSnapshot.findInstance(id);
- }
-
- public int getHeapIndex(Heap heap) {
- return mSnapshot.getHeapIndex(heap);
- }
-
- public Heap getHeap(String name) {
- return mSnapshot.getHeap(name);
- }
-
- /**
- * Returns a collection of instances whose immediate dominator is the
- * SENTINEL_ROOT.
- */
- public List<Instance> getRooted() {
- return mRooted;
- }
-
- /**
- * Returns true if the given instance is a root.
- */
- public boolean isRoot(Instance inst) {
- return mRoots.containsKey(inst);
- }
-
- /**
- * Returns the list of root types for the given instance, or null if the
- * instance is not a root.
- */
- public Collection<RootType> getRootTypes(Instance inst) {
- return mRoots.get(inst);
- }
-
- public List<Heap> getHeaps() {
- return mHeaps;
- }
-
- public Site getRootSite() {
- return mRootSite;
- }
-
- /**
- * Look up the site at which the given object was allocated.
- */
- public Site getSiteForInstance(Instance inst) {
- Site site = mRootSite;
- StackTrace stack = getStack(inst);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null) {
- List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
- site = mRootSite.getChild(path.iterator());
- }
- }
- return site;
- }
-
- /**
- * Return a list of those objects immediately dominated by the given
- * instance.
- */
- public List<Instance> getDominated(Instance inst) {
- return mDominated.get(inst);
- }
-
- /**
- * Return the total size of reachable objects allocated on the given heap.
- */
- public long getHeapSize(Heap heap) {
- return mHeapSizes.get(heap);
- }
-
- /**
- * Return the class name for the given class object.
- * classObj may be null, in which case "(class unknown)" is returned.
- */
- public static String getClassName(ClassObj classObj) {
- if (classObj == null) {
- return "(class unknown)";
- }
- return classObj.getClassName();
- }
-
- // Return the stack where the given instance was allocated.
- private static StackTrace getStack(Instance inst) {
- return inst.getStack();
- }
-
- // Return the list of stack frames for a stack trace.
- private static StackFrame[] getStackFrames(StackTrace stack) {
- return stack.getFrames();
- }
-
- // Return the serial number of the given stack trace.
- private static int getStackTraceSerialNumber(StackTrace stack) {
- return stack.getSerialNumber();
- }
-
- // Get the site associated with the given stack id and depth.
- // Returns the root site if no such site found.
- // depth of -1 means the full stack.
- public Site getSite(int stackId, int depth) {
- Site site = mRootSite;
- StackTrace stack = mSnapshot.getStackTrace(stackId);
- if (stack != null) {
- StackFrame[] frames = getStackFrames(stack);
- if (frames != null) {
- List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
- if (depth >= 0) {
- path = path.subList(0, depth);
- }
- site = mRootSite.getChild(path.iterator());
- }
- }
- return site;
- }
-
- // Return a list of known native allocations in the snapshot.
- public List<InstanceUtils.NativeAllocation> getNativeAllocations() {
- return mNativeAllocations;
- }
-}
diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/BitmapHandler.java
index 0f567e3..836aef6 100644
--- a/tools/ahat/src/BitmapHandler.java
+++ b/tools/ahat/src/BitmapHandler.java
@@ -16,7 +16,8 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.awt.image.BufferedImage;
@@ -38,9 +39,9 @@
Query query = new Query(exchange.getRequestURI());
long id = query.getLong("id", 0);
BufferedImage bitmap = null;
- Instance inst = mSnapshot.findInstance(id);
+ AhatInstance inst = mSnapshot.findInstance(id);
if (inst != null) {
- bitmap = InstanceUtils.asBitmap(inst);
+ bitmap = inst.asBitmap();
}
if (bitmap != null) {
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 7a673f5..c884e7f 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -16,8 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,39 +40,32 @@
* @param instances the collection of instances to generate a list for
*/
public static void render(final AhatSnapshot snapshot,
- Doc doc, Query query, String id, Collection<Instance> instances) {
- List<Instance> insts = new ArrayList<Instance>(instances);
+ Doc doc, Query query, String id, Collection<AhatInstance> instances) {
+ List<AhatInstance> insts = new ArrayList<AhatInstance>(instances);
Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
- HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts);
+ HeapTable.render(doc, query, id, new TableConfig(), snapshot, insts);
}
- private static class TableConfig implements HeapTable.TableConfig<Instance> {
- AhatSnapshot mSnapshot;
-
- public TableConfig(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
+ private static class TableConfig implements HeapTable.TableConfig<AhatInstance> {
@Override
public String getHeapsDescription() {
return "Bytes Retained by Heap";
}
@Override
- public long getSize(Instance element, Heap heap) {
- int index = mSnapshot.getHeapIndex(heap);
- return element.getRetainedSize(index);
+ public long getSize(AhatInstance element, AhatHeap heap) {
+ return element.getRetainedSize(heap);
}
@Override
- public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
- HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+ public List<HeapTable.ValueConfig<AhatInstance>> getValueConfigs() {
+ HeapTable.ValueConfig<AhatInstance> value = new HeapTable.ValueConfig<AhatInstance>() {
public String getDescription() {
return "Object";
}
- public DocString render(Instance element) {
- return Value.render(mSnapshot, element);
+ public DocString render(AhatInstance element) {
+ return Summarizer.summarize(element);
}
};
return Collections.singletonList(value);
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
index 5b84048..22188b0 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.java
@@ -16,7 +16,8 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -39,7 +40,7 @@
*/
public interface TableConfig<T> {
String getHeapsDescription();
- long getSize(T element, Heap heap);
+ long getSize(T element, AhatHeap heap);
List<ValueConfig<T>> getValueConfigs();
}
@@ -51,9 +52,9 @@
public static <T> void render(Doc doc, Query query, String id,
TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
// Only show the heaps that have non-zero entries.
- List<Heap> heaps = new ArrayList<Heap>();
- for (Heap heap : snapshot.getHeaps()) {
- if (hasNonZeroEntry(snapshot, heap, config, elements)) {
+ List<AhatHeap> heaps = new ArrayList<AhatHeap>();
+ for (AhatHeap heap : snapshot.getHeaps()) {
+ if (hasNonZeroEntry(heap, config, elements)) {
heaps.add(heap);
}
}
@@ -63,7 +64,7 @@
// Print the heap and values descriptions.
boolean showTotal = heaps.size() > 1;
List<Column> subcols = new ArrayList<Column>();
- for (Heap heap : heaps) {
+ for (AhatHeap heap : heaps) {
subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
}
if (showTotal) {
@@ -81,7 +82,7 @@
for (T elem : selector.selected()) {
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ for (AhatHeap heap : heaps) {
long size = config.getSize(elem, heap);
total += size;
vals.add(size == 0 ? DocString.text("") : DocString.format("%,14d", size));
@@ -99,20 +100,20 @@
// Print a summary of the remaining entries if there are any.
List<T> remaining = selector.remaining();
if (!remaining.isEmpty()) {
- Map<Heap, Long> summary = new HashMap<Heap, Long>();
- for (Heap heap : heaps) {
+ Map<AhatHeap, Long> summary = new HashMap<AhatHeap, Long>();
+ for (AhatHeap heap : heaps) {
summary.put(heap, 0L);
}
for (T elem : remaining) {
- for (Heap heap : heaps) {
+ for (AhatHeap heap : heaps) {
summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
}
}
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ for (AhatHeap heap : heaps) {
long size = summary.get(heap);
total += size;
vals.add(DocString.format("%,14d", size));
@@ -131,9 +132,9 @@
}
// Returns true if the given heap has a non-zero size entry.
- public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+ public static <T> boolean hasNonZeroEntry(AhatHeap heap,
TableConfig<T> config, List<T> elements) {
- if (snapshot.getHeapSize(heap) > 0) {
+ if (heap.getSize() > 0) {
for (T element : elements) {
if (config.getSize(element, heap) > 0) {
return true;
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
deleted file mode 100644
index a062afd..0000000
--- a/tools/ahat/src/InstanceUtils.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.Type;
-
-import java.awt.image.BufferedImage;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utilities for extracting information from hprof instances.
- */
-class InstanceUtils {
- /**
- * Returns true if the given instance is an instance of a class with the
- * given name.
- */
- private static boolean isInstanceOfClass(Instance inst, String className) {
- ClassObj cls = (inst == null) ? null : inst.getClassObj();
- return (cls != null && className.equals(cls.getClassName()));
- }
-
- /**
- * Read the byte[] value from an hprof Instance.
- * Returns null if the instance is not a byte array.
- */
- private static byte[] asByteArray(Instance inst) {
- if (!(inst instanceof ArrayInstance)) {
- return null;
- }
-
- ArrayInstance array = (ArrayInstance) inst;
- if (array.getArrayType() != Type.BYTE) {
- return null;
- }
-
- Object[] objs = array.getValues();
- byte[] bytes = new byte[objs.length];
- for (int i = 0; i < objs.length; i++) {
- Byte b = (Byte) objs[i];
- bytes[i] = b.byteValue();
- }
- return bytes;
- }
-
-
- /**
- * Read the string value from an hprof Instance.
- * Returns null if the object can't be interpreted as a string.
- */
- public static String asString(Instance inst) {
- return asString(inst, -1);
- }
-
- /**
- * Read the string value from an hprof Instance.
- * Returns null if the object can't be interpreted as a string.
- * The returned string is truncated to maxChars characters.
- * If maxChars is negative, the returned string is not truncated.
- */
- public static String asString(Instance inst, int maxChars) {
- // The inst object could either be a java.lang.String or a char[]. If it
- // is a char[], use that directly as the value, otherwise use the value
- // field of the string object. The field accesses for count and offset
- // later on will work okay regardless of what type the inst object is.
- boolean isString = isInstanceOfClass(inst, "java.lang.String");
- Object value = isString ? getField(inst, "value") : inst;
-
- if (!(value instanceof ArrayInstance)) {
- return null;
- }
-
- ArrayInstance chars = (ArrayInstance) value;
- int numChars = chars.getLength();
- int offset = getIntField(inst, "offset", 0);
- int count = getIntField(inst, "count", numChars);
-
- // With string compression enabled, the array type can be BYTE but in that case
- // offset must be 0 and count must match numChars.
- if (isString && (chars.getArrayType() == Type.BYTE) && (offset == 0) && (count == numChars)) {
- int length = (0 <= maxChars && maxChars < numChars) ? maxChars : numChars;
- return new String(chars.asRawByteArray(/* offset */ 0, length), StandardCharsets.US_ASCII);
- }
- if (chars.getArrayType() != Type.CHAR) {
- return null;
- }
- if (count == 0) {
- return "";
- }
- if (0 <= maxChars && maxChars < count) {
- count = maxChars;
- }
-
- int end = offset + count - 1;
- if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
- return new String(chars.asCharArray(offset, count));
- }
- return null;
- }
-
- /**
- * Read the bitmap data for the given android.graphics.Bitmap object.
- * Returns null if the object isn't for android.graphics.Bitmap or the
- * bitmap data couldn't be read.
- */
- public static BufferedImage asBitmap(Instance inst) {
- if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
- return null;
- }
-
- Integer width = getIntField(inst, "mWidth", null);
- if (width == null) {
- return null;
- }
-
- Integer height = getIntField(inst, "mHeight", null);
- if (height == null) {
- return null;
- }
-
- byte[] buffer = getByteArrayField(inst, "mBuffer");
- if (buffer == null) {
- return null;
- }
-
- // Convert the raw data to an image
- // Convert BGRA to ABGR
- int[] abgr = new int[height * width];
- for (int i = 0; i < abgr.length; i++) {
- abgr[i] = (
- (((int) buffer[i * 4 + 3] & 0xFF) << 24)
- + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
- + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
- + ((int) buffer[i * 4 + 2] & 0xFF));
- }
-
- BufferedImage bitmap = new BufferedImage(
- width, height, BufferedImage.TYPE_4BYTE_ABGR);
- bitmap.setRGB(0, 0, width, height, abgr, 0, width);
- return bitmap;
- }
-
- /**
- * Read a field of an instance.
- * Returns null if the field value is null or if the field couldn't be read.
- */
- public static Object getField(Instance inst, String fieldName) {
- if (!(inst instanceof ClassInstance)) {
- return null;
- }
-
- ClassInstance clsinst = (ClassInstance) inst;
- Object value = null;
- int count = 0;
- for (ClassInstance.FieldValue field : clsinst.getValues()) {
- if (fieldName.equals(field.getField().getName())) {
- value = field.getValue();
- count++;
- }
- }
- return count == 1 ? value : null;
- }
-
- /**
- * Read a reference field of an instance.
- * Returns null if the field value is null, or if the field couldn't be read.
- */
- public static Instance getRefField(Instance inst, String fieldName) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Instance)) {
- return null;
- }
- return (Instance) value;
- }
-
- /**
- * Read an int field of an instance.
- * The field is assumed to be an int type.
- * Returns <code>def</code> if the field value is not an int or could not be
- * read.
- */
- private static Integer getIntField(Instance inst, String fieldName, Integer def) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Integer)) {
- return def;
- }
- return (Integer) value;
- }
-
- /**
- * Read a long field of an instance.
- * The field is assumed to be a long type.
- * Returns <code>def</code> if the field value is not an long or could not
- * be read.
- */
- private static Long getLongField(Instance inst, String fieldName, Long def) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Long)) {
- return def;
- }
- return (Long) value;
- }
-
- /**
- * Read the given field from the given instance.
- * The field is assumed to be a byte[] field.
- * Returns null if the field value is null, not a byte[] or could not be read.
- */
- private static byte[] getByteArrayField(Instance inst, String fieldName) {
- Object value = getField(inst, fieldName);
- if (!(value instanceof Instance)) {
- return null;
- }
- return asByteArray((Instance) value);
- }
-
- // Return the bitmap instance associated with this object, or null if there
- // is none. This works for android.graphics.Bitmap instances and their
- // underlying Byte[] instances.
- public static Instance getAssociatedBitmapInstance(Instance inst) {
- ClassObj cls = inst.getClassObj();
- if (cls == null) {
- return null;
- }
-
- if ("android.graphics.Bitmap".equals(cls.getClassName())) {
- return inst;
- }
-
- if (inst instanceof ArrayInstance) {
- ArrayInstance array = (ArrayInstance) inst;
- if (array.getArrayType() == Type.BYTE && inst.getHardReverseReferences().size() == 1) {
- Instance ref = inst.getHardReverseReferences().get(0);
- ClassObj clsref = ref.getClassObj();
- if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
- return ref;
- }
- }
- }
- return null;
- }
-
- private static boolean isJavaLangRefReference(Instance inst) {
- ClassObj cls = (inst == null) ? null : inst.getClassObj();
- while (cls != null) {
- if ("java.lang.ref.Reference".equals(cls.getClassName())) {
- return true;
- }
- cls = cls.getSuperClassObj();
- }
- return false;
- }
-
- public static Instance getReferent(Instance inst) {
- if (isJavaLangRefReference(inst)) {
- return getRefField(inst, "referent");
- }
- return null;
- }
-
- /**
- * Assuming inst represents a DexCache object, return the dex location for
- * that dex cache. Returns null if the given instance doesn't represent a
- * DexCache object or the location could not be found.
- * If maxChars is non-negative, the returned location is truncated to
- * maxChars in length.
- */
- public static String getDexCacheLocation(Instance inst, int maxChars) {
- if (isInstanceOfClass(inst, "java.lang.DexCache")) {
- Instance location = getRefField(inst, "location");
- if (location != null) {
- return asString(location, maxChars);
- }
- }
- return null;
- }
-
- public static class NativeAllocation {
- public long size;
- public Heap heap;
- public long pointer;
- public Instance referent;
-
- public NativeAllocation(long size, Heap heap, long pointer, Instance referent) {
- this.size = size;
- this.heap = heap;
- this.pointer = pointer;
- this.referent = referent;
- }
- }
-
- /**
- * Assuming inst represents a NativeAllocation, return information about the
- * native allocation. Returns null if the given instance doesn't represent a
- * native allocation.
- */
- public static NativeAllocation getNativeAllocation(Instance inst) {
- if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) {
- return null;
- }
-
- Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null);
- if (pointer == null) {
- return null;
- }
-
- // Search for the registry field of inst.
- // Note: We know inst as an instance of ClassInstance because we already
- // read the nativePtr field from it.
- Instance registry = null;
- for (ClassInstance.FieldValue field : ((ClassInstance) inst).getValues()) {
- Object fieldValue = field.getValue();
- if (fieldValue instanceof Instance) {
- Instance fieldInst = (Instance) fieldValue;
- if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) {
- registry = fieldInst;
- break;
- }
- }
- }
-
- if (registry == null) {
- return null;
- }
-
- Long size = InstanceUtils.getLongField(registry, "size", null);
- if (size == null) {
- return null;
- }
-
- Instance referent = null;
- for (Instance ref : inst.getHardReverseReferences()) {
- if (isInstanceOfClass(ref, "sun.misc.Cleaner")) {
- referent = InstanceUtils.getReferent(ref);
- if (referent != null) {
- break;
- }
- }
- }
-
- if (referent == null) {
- return null;
- }
- return new NativeAllocation(size, inst.getHeap(), pointer, referent);
- }
-
- public static class PathElement {
- public final Instance instance;
- public final String field;
- public boolean isDominator;
-
- public PathElement(Instance instance, String field) {
- this.instance = instance;
- this.field = field;
- this.isDominator = false;
- }
- }
-
- /**
- * Returns a sample path from a GC root to this instance.
- * The given instance is included as the last element of the path with an
- * empty field description.
- */
- public static List<PathElement> getPathFromGcRoot(Instance inst) {
- List<PathElement> path = new ArrayList<PathElement>();
-
- Instance dom = inst;
- for (PathElement elem = new PathElement(inst, ""); elem != null;
- elem = getNextPathElementToGcRoot(elem.instance)) {
- if (elem.instance == dom) {
- elem.isDominator = true;
- dom = dom.getImmediateDominator();
- }
- path.add(elem);
- }
- Collections.reverse(path);
- return path;
- }
-
- /**
- * Returns the next instance to GC root from this object and a string
- * description of which field of that object refers to the given instance.
- * Returns null if the given instance has no next instance to the gc root.
- */
- private static PathElement getNextPathElementToGcRoot(Instance inst) {
- Instance parent = inst.getNextInstanceToGcRoot();
- if (parent == null || parent instanceof RootObj) {
- return null;
- }
-
- // Search the parent for the reference to the child.
- // TODO: This seems terribly inefficient. Can we use data structures to
- // help us here?
- String description = ".???";
- if (parent instanceof ArrayInstance) {
- ArrayInstance array = (ArrayInstance)parent;
- Object[] values = array.getValues();
- for (int i = 0; i < values.length; i++) {
- if (values[i] instanceof Instance) {
- Instance ref = (Instance)values[i];
- if (ref.getId() == inst.getId()) {
- description = String.format("[%d]", i);
- break;
- }
- }
- }
- } else if (parent instanceof ClassObj) {
- ClassObj cls = (ClassObj)parent;
- for (Map.Entry<Field, Object> entries : cls.getStaticFieldValues().entrySet()) {
- if (entries.getValue() instanceof Instance) {
- Instance ref = (Instance)entries.getValue();
- if (ref.getId() == inst.getId()) {
- description = "." + entries.getKey().getName();
- break;
- }
- }
- }
- } else if (parent instanceof ClassInstance) {
- ClassInstance obj = (ClassInstance)parent;
- for (ClassInstance.FieldValue fields : obj.getValues()) {
- if (fields.getValue() instanceof Instance) {
- Instance ref = (Instance)fields.getValue();
- if (ref.getId() == inst.getId()) {
- description = "." + fields.getField().getName();
- break;
- }
- }
- }
- }
- return new PathElement(parent, description);
- }
-}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index c79b578..405ac77 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,6 +16,7 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
import com.android.tools.perflib.heap.ProguardMap;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
@@ -99,6 +100,7 @@
server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
server.setExecutor(Executors.newFixedThreadPool(1));
System.out.println("Server started on localhost:" + port);
+
server.start();
}
}
diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java
index 17407e1..605a067 100644
--- a/tools/ahat/src/NativeAllocationsHandler.java
+++ b/tools/ahat/src/NativeAllocationsHandler.java
@@ -16,6 +16,8 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.NativeAllocation;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
@@ -32,13 +34,13 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
+ List<NativeAllocation> allocs = mSnapshot.getNativeAllocations();
doc.title("Registered Native Allocations");
doc.section("Overview");
long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
+ for (NativeAllocation alloc : allocs) {
totalSize += alloc.size;
}
doc.descriptions();
@@ -57,26 +59,26 @@
new Column("Heap"),
new Column("Native Pointer"),
new Column("Referent"));
- Comparator<InstanceUtils.NativeAllocation> compare
- = new Sort.WithPriority<InstanceUtils.NativeAllocation>(
+ Comparator<NativeAllocation> compare
+ = new Sort.WithPriority<NativeAllocation>(
new Sort.NativeAllocationByHeapName(),
new Sort.NativeAllocationBySize());
Collections.sort(allocs, compare);
- SubsetSelector<InstanceUtils.NativeAllocation> selector
+ SubsetSelector<NativeAllocation> selector
= new SubsetSelector(query, ALLOCATIONS_ID, allocs);
- for (InstanceUtils.NativeAllocation alloc : selector.selected()) {
+ for (NativeAllocation alloc : selector.selected()) {
doc.row(
DocString.format("%,14d", alloc.size),
DocString.text(alloc.heap.getName()),
DocString.format("0x%x", alloc.pointer),
- Value.render(mSnapshot, alloc.referent));
+ Summarizer.summarize(alloc.referent));
}
// Print a summary of the remaining entries if there are any.
- List<InstanceUtils.NativeAllocation> remaining = selector.remaining();
+ List<NativeAllocation> remaining = selector.remaining();
if (!remaining.isEmpty()) {
long total = 0;
- for (InstanceUtils.NativeAllocation alloc : remaining) {
+ for (NativeAllocation alloc : remaining) {
total += alloc.size;
}
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 78aac17..2546b0c 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -16,22 +16,21 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootType;
+import com.android.ahat.heapdump.AhatArrayInstance;
+import com.android.ahat.heapdump.AhatClassInstance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import static com.android.ahat.InstanceUtils.PathElement;
class ObjectHandler implements AhatHandler {
@@ -53,35 +52,35 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
long id = query.getLong("id", 0);
- Instance inst = mSnapshot.findInstance(id);
+ AhatInstance inst = mSnapshot.findInstance(id);
if (inst == null) {
doc.println(DocString.format("No object with id %08xl", id));
return;
}
- doc.title("Object %08x", inst.getUniqueId());
- doc.big(Value.render(mSnapshot, inst));
+ doc.title("Object %08x", inst.getId());
+ doc.big(Summarizer.summarize(inst));
printAllocationSite(doc, query, inst);
printGcRootPath(doc, query, inst);
doc.section("Object Info");
- ClassObj cls = inst.getClassObj();
+ AhatClassObj cls = inst.getClassObj();
doc.descriptions();
- doc.description(DocString.text("Class"), Value.render(mSnapshot, cls));
+ doc.description(DocString.text("Class"), Summarizer.summarize(cls));
doc.description(DocString.text("Size"), DocString.format("%d", inst.getSize()));
doc.description(
DocString.text("Retained Size"),
DocString.format("%d", inst.getTotalRetainedSize()));
doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
- Collection<RootType> rootTypes = mSnapshot.getRootTypes(inst);
+ Collection<String> rootTypes = inst.getRootTypes();
if (rootTypes != null) {
DocString types = new DocString();
String comma = "";
- for (RootType type : rootTypes) {
+ for (String type : rootTypes) {
types.append(comma);
- types.append(type.getName());
+ types.append(type);
comma = ", ";
}
doc.description(DocString.text("Root Types"), types);
@@ -90,112 +89,106 @@
doc.end();
printBitmap(doc, inst);
- if (inst instanceof ClassInstance) {
- printClassInstanceFields(doc, query, mSnapshot, (ClassInstance)inst);
- } else if (inst instanceof ArrayInstance) {
- printArrayElements(doc, query, mSnapshot, (ArrayInstance)inst);
- } else if (inst instanceof ClassObj) {
- printClassInfo(doc, query, mSnapshot, (ClassObj)inst);
+ if (inst.isClassInstance()) {
+ printClassInstanceFields(doc, query, inst.asClassInstance());
+ } else if (inst.isArrayInstance()) {
+ printArrayElements(doc, query, inst.asArrayInstance());
+ } else if (inst.isClassObj()) {
+ printClassInfo(doc, query, inst.asClassObj());
}
- printReferences(doc, query, mSnapshot, inst);
+ printReferences(doc, query, inst);
printDominatedObjects(doc, query, inst);
}
- private static void printClassInstanceFields(
- Doc doc, Query query, AhatSnapshot snapshot, ClassInstance inst) {
+ private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
doc.section("Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- SubsetSelector<ClassInstance.FieldValue> selector
- = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues());
- for (ClassInstance.FieldValue field : selector.selected()) {
+ SubsetSelector<FieldValue> selector
+ = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getInstanceFields());
+ for (FieldValue field : selector.selected()) {
doc.row(
- DocString.text(field.getField().getType().toString()),
- DocString.text(field.getField().getName()),
- Value.render(snapshot, field.getValue()));
+ DocString.text(field.getType()),
+ DocString.text(field.getName()),
+ Summarizer.summarize(field.getValue()));
}
doc.end();
selector.render(doc);
}
- private static void printArrayElements(
- Doc doc, Query query, AhatSnapshot snapshot, ArrayInstance array) {
+ private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
doc.section("Array Elements");
doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
- List<Object> elements = Arrays.asList(array.getValues());
- SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
+ List<Value> elements = array.getValues();
+ SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
int i = 0;
- for (Object elem : selector.selected()) {
- doc.row(DocString.format("%d", i), Value.render(snapshot, elem));
+ for (Value elem : selector.selected()) {
+ doc.row(DocString.format("%d", i), Summarizer.summarize(elem));
i++;
}
doc.end();
selector.render(doc);
}
- private static void printClassInfo(
- Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) {
+ private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
doc.section("Class Info");
doc.descriptions();
doc.description(DocString.text("Super Class"),
- Value.render(snapshot, clsobj.getSuperClassObj()));
+ Summarizer.summarize(clsobj.getSuperClassObj()));
doc.description(DocString.text("Class Loader"),
- Value.render(snapshot, clsobj.getClassLoader()));
+ Summarizer.summarize(clsobj.getClassLoader()));
doc.end();
doc.section("Static Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- List<Map.Entry<Field, Object>> fields
- = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet());
- SubsetSelector<Map.Entry<Field, Object>> selector
- = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
- for (Map.Entry<Field, Object> field : selector.selected()) {
+ List<FieldValue> fields = clsobj.getStaticFieldValues();
+ SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+ for (FieldValue field : selector.selected()) {
doc.row(
- DocString.text(field.getKey().getType().toString()),
- DocString.text(field.getKey().getName()),
- Value.render(snapshot, field.getValue()));
+ DocString.text(field.getType()),
+ DocString.text(field.getName()),
+ Summarizer.summarize(field.getValue()));
}
doc.end();
selector.render(doc);
}
- private static void printReferences(
- Doc doc, Query query, AhatSnapshot snapshot, Instance inst) {
+ private static void printReferences(Doc doc, Query query, AhatInstance inst) {
doc.section("Objects with References to this Object");
if (inst.getHardReverseReferences().isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
doc.table(new Column("Object"));
- List<Instance> references = inst.getHardReverseReferences();
- SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
- for (Instance ref : selector.selected()) {
- doc.row(Value.render(snapshot, ref));
+ List<AhatInstance> references = inst.getHardReverseReferences();
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
+ for (AhatInstance ref : selector.selected()) {
+ doc.row(Summarizer.summarize(ref));
}
doc.end();
selector.render(doc);
}
- if (inst.getSoftReverseReferences() != null) {
+ if (!inst.getSoftReverseReferences().isEmpty()) {
doc.section("Objects with Soft References to this Object");
doc.table(new Column("Object"));
- List<Instance> references = inst.getSoftReverseReferences();
- SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
- for (Instance ref : selector.selected()) {
- doc.row(Value.render(snapshot, ref));
+ List<AhatInstance> references = inst.getSoftReverseReferences();
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+ for (AhatInstance ref : selector.selected()) {
+ doc.row(Summarizer.summarize(ref));
}
doc.end();
selector.render(doc);
}
}
- private void printAllocationSite(Doc doc, Query query, Instance inst) {
+ private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
doc.section("Allocation Site");
- Site site = mSnapshot.getSiteForInstance(inst);
+ Site site = inst.getSite();
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
}
// Draw the bitmap corresponding to this instance if there is one.
- private static void printBitmap(Doc doc, Instance inst) {
- Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+ private static void printBitmap(Doc doc, AhatInstance inst) {
+ AhatInstance bitmap = inst.getAssociatedBitmapInstance();
if (bitmap != null) {
doc.section("Bitmap Image");
doc.println(DocString.image(
@@ -203,9 +196,9 @@
}
}
- private void printGcRootPath(Doc doc, Query query, Instance inst) {
+ private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
doc.section("Sample Path from GC Root");
- List<PathElement> path = InstanceUtils.getPathFromGcRoot(inst);
+ List<PathElement> path = inst.getPathFromGcRoot();
// Add 'null' as a marker for the root.
path.add(0, null);
@@ -215,13 +208,12 @@
return "Bytes Retained by Heap (Dominators Only)";
}
- public long getSize(PathElement element, Heap heap) {
+ public long getSize(PathElement element, AhatHeap heap) {
if (element == null) {
- return mSnapshot.getHeapSize(heap);
+ return heap.getSize();
}
if (element.isDominator) {
- int index = mSnapshot.getHeapIndex(heap);
- return element.instance.getRetainedSize(index);
+ return element.instance.getRetainedSize(heap);
}
return 0;
}
@@ -236,8 +228,8 @@
if (element == null) {
return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
} else {
- DocString label = DocString.text(" → ");
- label.append(Value.render(mSnapshot, element.instance));
+ DocString label = DocString.text("→ ");
+ label.append(Summarizer.summarize(element.instance));
label.append(element.field);
return label;
}
@@ -249,9 +241,9 @@
HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
}
- public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+ public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
doc.section("Immediately Dominated Objects");
- List<Instance> instances = mSnapshot.getDominated(inst);
+ List<AhatInstance> instances = inst.getDominated();
if (instances != null) {
DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
} else {
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 4cfb0a5..4126474 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -16,7 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,17 +35,16 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
- int stackId = query.getInt("stack", 0);
+ int id = query.getInt("id", 0);
int depth = query.getInt("depth", 0);
String className = query.get("class", null);
String heapName = query.get("heap", null);
- Site site = mSnapshot.getSite(stackId, depth);
+ Site site = mSnapshot.getSite(id, depth);
- List<Instance> insts = new ArrayList<Instance>();
- for (Instance inst : site.getObjects()) {
+ List<AhatInstance> insts = new ArrayList<AhatInstance>();
+ for (AhatInstance inst : site.getObjects()) {
if ((heapName == null || inst.getHeap().getName().equals(heapName))
- && (className == null
- || AhatSnapshot.getClassName(inst.getClassObj()).equals(className))) {
+ && (className == null || inst.getClassName().equals(className))) {
insts.add(inst);
}
}
@@ -55,12 +56,12 @@
new Column("Size", Column.Align.RIGHT),
new Column("Heap"),
new Column("Object"));
- SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
- for (Instance inst : selector.selected()) {
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+ for (AhatInstance inst : selector.selected()) {
doc.row(
DocString.format("%,d", inst.getSize()),
DocString.text(inst.getHeap().getName()),
- Value.render(mSnapshot, inst));
+ Summarizer.summarize(inst));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index 0dbad7e..3a34d13 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -16,9 +16,11 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
-import java.io.IOException;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.NativeAllocation;
import java.io.File;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -49,11 +51,11 @@
doc.section("Heap Sizes");
printHeapSizes(doc, query);
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
+ List<NativeAllocation> allocs = mSnapshot.getNativeAllocations();
if (!allocs.isEmpty()) {
doc.section("Registered Native Allocations");
long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
+ for (NativeAllocation alloc : allocs) {
totalSize += alloc.size;
}
doc.descriptions();
@@ -75,8 +77,8 @@
return "Bytes Retained by Heap";
}
- public long getSize(Object element, Heap heap) {
- return mSnapshot.getHeapSize(heap);
+ public long getSize(Object element, AhatHeap heap) {
+ return heap.getSize();
}
public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
diff --git a/tools/ahat/src/RootedHandler.java b/tools/ahat/src/RootedHandler.java
index ec3272f..26451a3 100644
--- a/tools/ahat/src/RootedHandler.java
+++ b/tools/ahat/src/RootedHandler.java
@@ -16,6 +16,7 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.io.IOException;
class RootedHandler implements AhatHandler {
diff --git a/tools/ahat/src/Site.java b/tools/ahat/src/Site.java
deleted file mode 100644
index dbb84f6..0000000
--- a/tools/ahat/src/Site.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.StackFrame;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-class Site {
- // The site that this site was directly called from.
- // mParent is null for the root site.
- private Site mParent;
-
- // A description of the Site. Currently this is used to uniquely identify a
- // site within its parent.
- private String mName;
-
- // To identify this site, we pick one stack trace where we have seen the
- // site. mStackId is the id for that stack trace, and mStackDepth is the
- // depth of this site in that stack trace.
- // For the root site, mStackId is 0 and mStackDepth is 0.
- private int mStackId;
- private int mStackDepth;
-
- // Mapping from heap name to the total size of objects allocated in this
- // site (including child sites) on the given heap.
- private Map<String, Long> mSizesByHeap;
-
- // Mapping from child site name to child site.
- private Map<String, Site> mChildren;
-
- // List of all objects allocated in this site (including child sites).
- private List<Instance> mObjects;
- private List<ObjectsInfo> mObjectsInfos;
- private Map<Heap, Map<ClassObj, ObjectsInfo>> mObjectsInfoMap;
-
- public static class ObjectsInfo {
- public Heap heap;
- public ClassObj classObj;
- public long numInstances;
- public long numBytes;
-
- public ObjectsInfo(Heap heap, ClassObj classObj, long numInstances, long numBytes) {
- this.heap = heap;
- this.classObj = classObj;
- this.numInstances = numInstances;
- this.numBytes = numBytes;
- }
- }
-
- /**
- * Construct a root site.
- */
- public Site(String name) {
- this(null, name, 0, 0);
- }
-
- public Site(Site parent, String name, int stackId, int stackDepth) {
- mParent = parent;
- mName = name;
- mStackId = stackId;
- mStackDepth = stackDepth;
- mSizesByHeap = new HashMap<String, Long>();
- mChildren = new HashMap<String, Site>();
- mObjects = new ArrayList<Instance>();
- mObjectsInfos = new ArrayList<ObjectsInfo>();
- mObjectsInfoMap = new HashMap<Heap, Map<ClassObj, ObjectsInfo>>();
- }
-
- /**
- * Add an instance to this site.
- * Returns the site at which the instance was allocated.
- */
- public Site add(int stackId, int stackDepth, Iterator<StackFrame> path, Instance inst) {
- mObjects.add(inst);
-
- String heap = inst.getHeap().getName();
- mSizesByHeap.put(heap, getSize(heap) + inst.getSize());
-
- Map<ClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(inst.getHeap());
- if (classToObjectsInfo == null) {
- classToObjectsInfo = new HashMap<ClassObj, ObjectsInfo>();
- mObjectsInfoMap.put(inst.getHeap(), classToObjectsInfo);
- }
-
- ObjectsInfo info = classToObjectsInfo.get(inst.getClassObj());
- if (info == null) {
- info = new ObjectsInfo(inst.getHeap(), inst.getClassObj(), 0, 0);
- mObjectsInfos.add(info);
- classToObjectsInfo.put(inst.getClassObj(), info);
- }
-
- info.numInstances++;
- info.numBytes += inst.getSize();
-
- if (path.hasNext()) {
- String next = path.next().toString();
- Site child = mChildren.get(next);
- if (child == null) {
- child = new Site(this, next, stackId, stackDepth + 1);
- mChildren.put(next, child);
- }
- return child.add(stackId, stackDepth + 1, path, inst);
- } else {
- return this;
- }
- }
-
- // Get the size of a site for a specific heap.
- public long getSize(String heap) {
- Long val = mSizesByHeap.get(heap);
- if (val == null) {
- return 0;
- }
- return val;
- }
-
- /**
- * Get the list of objects allocated under this site. Includes objects
- * allocated in children sites.
- */
- public Collection<Instance> getObjects() {
- return mObjects;
- }
-
- public List<ObjectsInfo> getObjectsInfos() {
- return mObjectsInfos;
- }
-
- // Get the combined size of the site for all heaps.
- public long getTotalSize() {
- long size = 0;
- for (Long val : mSizesByHeap.values()) {
- size += val;
- }
- return size;
- }
-
- /**
- * Return the site this site was called from.
- * Returns null for the root site.
- */
- public Site getParent() {
- return mParent;
- }
-
- public String getName() {
- return mName;
- }
-
- // Returns the hprof id of a stack this site appears on.
- public int getStackId() {
- return mStackId;
- }
-
- // Returns the stack depth of this site in the stack whose id is returned
- // by getStackId().
- public int getStackDepth() {
- return mStackDepth;
- }
-
- List<Site> getChildren() {
- return new ArrayList<Site>(mChildren.values());
- }
-
- // Get the child at the given path relative to this site.
- // Returns null if no such child found.
- Site getChild(Iterator<StackFrame> path) {
- if (path.hasNext()) {
- String next = path.next().toString();
- Site child = mChildren.get(next);
- return (child == null) ? null : child.getChild(path);
- } else {
- return this;
- }
- }
-}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 839e220..cfd5c9a 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -16,7 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
@@ -35,11 +37,13 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
- int stackId = query.getInt("stack", 0);
- int depth = query.getInt("depth", -1);
- Site site = mSnapshot.getSite(stackId, depth);
+ int id = query.getInt("id", 0);
+ int depth = query.getInt("depth", 0);
+ Site site = mSnapshot.getSite(id, depth);
- doc.title("Site %s", site.getName());
+ doc.title("Site");
+ doc.big(Summarizer.summarize(site));
+
doc.section("Allocation Site");
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
@@ -48,15 +52,14 @@
if (children.isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
- Collections.sort(children, new Sort.SiteBySize("app"));
-
+ Collections.sort(children, Sort.defaultSiteCompare(mSnapshot));
HeapTable.TableConfig<Site> table = new HeapTable.TableConfig<Site>() {
public String getHeapsDescription() {
return "Reachable Bytes Allocated on Heap";
}
- public long getSize(Site element, Heap heap) {
- return element.getSize(heap.getName());
+ public long getSize(Site element, AhatHeap heap) {
+ return element.getSize(heap);
}
public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -66,10 +69,7 @@
}
public DocString render(Site element) {
- return DocString.link(
- DocString.formattedUri("site?stack=%d&depth=%d",
- element.getStackId(), element.getStackDepth()),
- DocString.text(element.getName()));
+ return Summarizer.summarize(element);
}
};
return Collections.singletonList(value);
@@ -93,15 +93,15 @@
SubsetSelector<Site.ObjectsInfo> selector
= new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
for (Site.ObjectsInfo info : selector.selected()) {
- String className = AhatSnapshot.getClassName(info.classObj);
+ String className = info.getClassName();
doc.row(
DocString.format("%,14d", info.numBytes),
DocString.link(
- DocString.formattedUri("objects?stack=%d&depth=%d&heap=%s&class=%s",
- site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
+ DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s",
+ site.getId(), site.getDepth(), info.heap.getName(), className),
DocString.format("%,14d", info.numInstances)),
DocString.text(info.heap.getName()),
- Value.render(mSnapshot, info.classObj));
+ Summarizer.summarize(info.classObj));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
index 2c06b47..21ca2de 100644
--- a/tools/ahat/src/SitePrinter.java
+++ b/tools/ahat/src/SitePrinter.java
@@ -16,7 +16,9 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -35,8 +37,8 @@
return "Reachable Bytes Allocated on Heap";
}
- public long getSize(Site element, Heap heap) {
- return element.getSize(heap.getName());
+ public long getSize(Site element, AhatHeap heap) {
+ return element.getSize(heap);
}
public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -50,11 +52,7 @@
if (element.getParent() != null) {
str.append("→ ");
}
- str.appendLink(
- DocString.formattedUri("site?stack=%d&depth=%d",
- element.getStackId(), element.getStackDepth()),
- DocString.text(element.getName()));
- return str;
+ return str.append(Summarizer.summarize(element));
}
};
return Collections.singletonList(value);
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
index 8a3d9f2..6b93fbc 100644
--- a/tools/ahat/src/Sort.java
+++ b/tools/ahat/src/Sort.java
@@ -16,9 +16,11 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.NativeAllocation;
+import com.android.ahat.heapdump.Site;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -38,9 +40,9 @@
* Compare instances by their instance id.
* This sorts instances from smaller id to larger id.
*/
- public static class InstanceById implements Comparator<Instance> {
+ public static class InstanceById implements Comparator<AhatInstance> {
@Override
- public int compare(Instance a, Instance b) {
+ public int compare(AhatInstance a, AhatInstance b) {
return Long.compare(a.getId(), b.getId());
}
}
@@ -51,9 +53,9 @@
* equal for the purposes of comparison.
* This sorts instances from larger retained size to smaller retained size.
*/
- public static class InstanceByTotalRetainedSize implements Comparator<Instance> {
+ public static class InstanceByTotalRetainedSize implements Comparator<AhatInstance> {
@Override
- public int compare(Instance a, Instance b) {
+ public int compare(AhatInstance a, AhatInstance b) {
return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
}
}
@@ -64,20 +66,16 @@
* equal for the purposes of comparison.
* This sorts instances from larger retained size to smaller retained size.
*/
- public static class InstanceByHeapRetainedSize implements Comparator<Instance> {
- private int mIndex;
+ public static class InstanceByHeapRetainedSize implements Comparator<AhatInstance> {
+ private AhatHeap mHeap;
- public InstanceByHeapRetainedSize(AhatSnapshot snapshot, Heap heap) {
- mIndex = snapshot.getHeapIndex(heap);
- }
-
- public InstanceByHeapRetainedSize(int heapIndex) {
- mIndex = heapIndex;
+ public InstanceByHeapRetainedSize(AhatHeap heap) {
+ mHeap = heap;
}
@Override
- public int compare(Instance a, Instance b) {
- return Long.compare(b.getRetainedSize(mIndex), a.getRetainedSize(mIndex));
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(b.getRetainedSize(mHeap), a.getRetainedSize(mHeap));
}
}
@@ -107,18 +105,18 @@
}
}
- public static Comparator<Instance> defaultInstanceCompare(AhatSnapshot snapshot) {
- List<Comparator<Instance>> comparators = new ArrayList<Comparator<Instance>>();
+ public static Comparator<AhatInstance> defaultInstanceCompare(AhatSnapshot snapshot) {
+ List<Comparator<AhatInstance>> comparators = new ArrayList<Comparator<AhatInstance>>();
// Priority goes to the app heap, if we can find one.
- Heap appHeap = snapshot.getHeap("app");
+ AhatHeap appHeap = snapshot.getHeap("app");
if (appHeap != null) {
- comparators.add(new InstanceByHeapRetainedSize(snapshot, appHeap));
+ comparators.add(new InstanceByHeapRetainedSize(appHeap));
}
// Next is by total retained size.
comparators.add(new InstanceByTotalRetainedSize());
- return new WithPriority<Instance>(comparators);
+ return new WithPriority<AhatInstance>(comparators);
}
/**
@@ -127,10 +125,10 @@
* considered equal for the purposes of comparison.
* This sorts sites from larger size to smaller size.
*/
- public static class SiteBySize implements Comparator<Site> {
- String mHeap;
+ public static class SiteByHeapSize implements Comparator<Site> {
+ AhatHeap mHeap;
- public SiteBySize(String heap) {
+ public SiteByHeapSize(AhatHeap heap) {
mHeap = heap;
}
@@ -141,6 +139,31 @@
}
/**
+ * Compare Sites by the total size of objects allocated.
+ * This sorts sites from larger size to smaller size.
+ */
+ public static class SiteByTotalSize implements Comparator<Site> {
+ @Override
+ public int compare(Site a, Site b) {
+ return Long.compare(b.getTotalSize(), a.getTotalSize());
+ }
+ }
+
+ public static Comparator<Site> defaultSiteCompare(AhatSnapshot snapshot) {
+ List<Comparator<Site>> comparators = new ArrayList<Comparator<Site>>();
+
+ // Priority goes to the app heap, if we can find one.
+ AhatHeap appHeap = snapshot.getHeap("app");
+ if (appHeap != null) {
+ comparators.add(new SiteByHeapSize(appHeap));
+ }
+
+ // Next is by total size.
+ comparators.add(new SiteByTotalSize());
+ return new WithPriority<Site>(comparators);
+ }
+
+ /**
* Compare Site.ObjectsInfo by their size.
* Different object infos with the same total retained size are considered
* equal for the purposes of comparison.
@@ -173,34 +196,34 @@
public static class ObjectsInfoByClassName implements Comparator<Site.ObjectsInfo> {
@Override
public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
- String aName = AhatSnapshot.getClassName(a.classObj);
- String bName = AhatSnapshot.getClassName(b.classObj);
+ String aName = a.getClassName();
+ String bName = b.getClassName();
return aName.compareTo(bName);
}
}
/**
- * Compare AhatSnapshot.NativeAllocation by heap name.
+ * Compare NativeAllocation by heap name.
* Different allocations with the same heap name are considered equal for
* the purposes of comparison.
*/
public static class NativeAllocationByHeapName
- implements Comparator<InstanceUtils.NativeAllocation> {
+ implements Comparator<NativeAllocation> {
@Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
+ public int compare(NativeAllocation a, NativeAllocation b) {
return a.heap.getName().compareTo(b.heap.getName());
}
}
/**
- * Compare InstanceUtils.NativeAllocation by their size.
+ * Compare NativeAllocation by their size.
* Different allocations with the same size are considered equal for the
* purposes of comparison.
* This sorts allocations from larger size to smaller size.
*/
- public static class NativeAllocationBySize implements Comparator<InstanceUtils.NativeAllocation> {
+ public static class NativeAllocationBySize implements Comparator<NativeAllocation> {
@Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
+ public int compare(NativeAllocation a, NativeAllocation b) {
return Long.compare(b.size, a.size);
}
}
diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/StaticHandler.java
index fb7049d..b2805d6 100644
--- a/tools/ahat/src/StaticHandler.java
+++ b/tools/ahat/src/StaticHandler.java
@@ -17,10 +17,10 @@
package com.android.ahat;
import com.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
-import java.io.InputStream;
+import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Summarizer.java
similarity index 61%
rename from tools/ahat/src/Value.java
rename to tools/ahat/src/Summarizer.java
index 847692b..40a0499 100644
--- a/tools/ahat/src/Value.java
+++ b/tools/ahat/src/Summarizer.java
@@ -16,23 +16,24 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
import java.net.URI;
/**
- * Class to render an hprof value to a DocString.
+ * Class for generating a DocString summary of an instance or value.
*/
-class Value {
+class Summarizer {
// For string literals, we limit the number of characters we show to
// kMaxChars in case the string is really long.
private static int kMaxChars = 200;
/**
- * Create a DocString representing a summary of the given instance.
+ * Creates a DocString representing a summary of the given instance.
*/
- private static DocString renderInstance(AhatSnapshot snapshot, Instance inst) {
+ public static DocString summarize(AhatInstance inst) {
DocString formatted = new DocString();
if (inst == null) {
formatted.append("(null)");
@@ -40,14 +41,13 @@
}
// Annotate roots as roots.
- if (snapshot.isRoot(inst)) {
+ if (inst.isRoot()) {
formatted.append("(root) ");
}
-
// Annotate classes as classes.
DocString link = new DocString();
- if (inst instanceof ClassObj) {
+ if (inst.isClassObj()) {
link.append("class ");
}
@@ -57,25 +57,25 @@
formatted.appendLink(objTarget, link);
// Annotate Strings with their values.
- String stringValue = InstanceUtils.asString(inst, kMaxChars);
+ String stringValue = inst.asString(kMaxChars);
if (stringValue != null) {
formatted.appendFormat(" \"%s", stringValue);
formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
}
// Annotate Reference with its referent
- Instance referent = InstanceUtils.getReferent(inst);
+ AhatInstance referent = inst.getReferent();
if (referent != null) {
formatted.append(" for ");
// It should not be possible for a referent to refer back to the
// reference object, even indirectly, so there shouldn't be any issues
// with infinite recursion here.
- formatted.append(renderInstance(snapshot, referent));
+ formatted.append(summarize(referent));
}
// Annotate DexCache with its location.
- String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst, kMaxChars);
+ String dexCacheLocation = inst.getDexCacheLocation(kMaxChars);
if (dexCacheLocation != null) {
formatted.appendFormat(" for %s", dexCacheLocation);
if (kMaxChars == dexCacheLocation.length()) {
@@ -85,7 +85,7 @@
// Annotate bitmaps with a thumbnail.
- Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+ AhatInstance bitmap = inst.getAssociatedBitmapInstance();
String thumbnail = "";
if (bitmap != null) {
URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
@@ -95,13 +95,30 @@
}
/**
- * Create a DocString summarizing the given value.
+ * Creates a DocString summarizing the given value.
*/
- public static DocString render(AhatSnapshot snapshot, Object val) {
- if (val instanceof Instance) {
- return renderInstance(snapshot, (Instance)val);
- } else {
- return DocString.format("%s", val);
+ public static DocString summarize(Value value) {
+ if (value == null) {
+ return DocString.text("null");
}
+ if (value.isAhatInstance()) {
+ return summarize(value.asAhatInstance());
+ }
+ return DocString.text(value.toString());
+ }
+
+ /**
+ * Creates a DocString summarizing the given site.
+ */
+ public static DocString summarize(Site site) {
+ DocString text = DocString.text(site.getMethodName());
+ text.append(site.getSignature());
+ text.append(" - ");
+ text.append(site.getFilename());
+ if (site.getLineNumber() > 0) {
+ text.append(":").append(Integer.toString(site.getLineNumber()));
+ }
+ URI uri = DocString.formattedUri("site?id=%d&depth=%d", site.getId(), site.getDepth());
+ return DocString.link(uri, text);
}
}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
new file mode 100644
index 0000000..d88cf94
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractList;
+import java.util.List;
+
+public class AhatArrayInstance extends AhatInstance {
+ // To save space, we store byte, character, and object arrays directly as
+ // byte, character, and AhatInstance arrays respectively. This is especially
+ // important for large byte arrays, such as bitmaps. All other array types
+ // are stored as an array of objects, though we could potentially save space
+ // by specializing those too. mValues is a list view of the underlying
+ // array.
+ private List<Value> mValues;
+ private byte[] mByteArray; // null if not a byte array.
+ private char[] mCharArray; // null if not a char array.
+
+ public AhatArrayInstance(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ArrayInstance array = (ArrayInstance)inst;
+ switch (array.getArrayType()) {
+ case OBJECT:
+ Object[] objects = array.getValues();
+ final AhatInstance[] insts = new AhatInstance[objects.length];
+ for (int i = 0; i < objects.length; i++) {
+ if (objects[i] != null) {
+ Instance ref = (Instance)objects[i];
+ insts[i] = snapshot.findInstance(ref.getId());
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ String field = "[" + Integer.toString(i) + "]";
+ insts[i].setNextInstanceToGcRoot(this, field);
+ }
+ }
+ }
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return insts.length;
+ }
+
+ @Override public Value get(int index) {
+ AhatInstance obj = insts[index];
+ return obj == null ? null : new Value(insts[index]);
+ }
+ };
+ break;
+
+ case CHAR:
+ final char[] chars = array.asCharArray(0, array.getLength());
+ mCharArray = chars;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return chars.length;
+ }
+
+ @Override public Value get(int index) {
+ return new Value(chars[index]);
+ }
+ };
+ break;
+
+ case BYTE:
+ final byte[] bytes = array.asRawByteArray(0, array.getLength());
+ mByteArray = bytes;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return bytes.length;
+ }
+
+ @Override public Value get(int index) {
+ return new Value(bytes[index]);
+ }
+ };
+ break;
+
+ default:
+ final Object[] values = array.getValues();
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return values.length;
+ }
+
+ @Override public Value get(int index) {
+ Object obj = values[index];
+ return obj == null ? null : new Value(obj);
+ }
+ };
+ break;
+ }
+ }
+
+ /**
+ * Returns the length of the array.
+ */
+ public int getLength() {
+ return mValues.size();
+ }
+
+ /**
+ * Returns the array's values.
+ */
+ public List<Value> getValues() {
+ return mValues;
+ }
+
+ /**
+ * Returns the object at the given index of this array.
+ */
+ public Value getValue(int index) {
+ return mValues.get(index);
+ }
+
+ @Override public boolean isArrayInstance() {
+ return true;
+ }
+
+ @Override public AhatArrayInstance asArrayInstance() {
+ return this;
+ }
+
+ @Override public String asString(int maxChars) {
+ return asString(0, getLength(), maxChars);
+ }
+
+ /**
+ * Returns the String value associated with this array.
+ * Only char arrays are considered as having an associated String value.
+ */
+ String asString(int offset, int count, int maxChars) {
+ if (mCharArray == null) {
+ return null;
+ }
+
+ if (count == 0) {
+ return "";
+ }
+ int numChars = mCharArray.length;
+ if (0 <= maxChars && maxChars < count) {
+ count = maxChars;
+ }
+
+ int end = offset + count - 1;
+ if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+ return new String(mCharArray, offset, count);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the ascii String value associated with this array.
+ * Only byte arrays are considered as having an associated ascii String value.
+ */
+ String asAsciiString(int offset, int count, int maxChars) {
+ if (mByteArray == null) {
+ return null;
+ }
+
+ if (count == 0) {
+ return "";
+ }
+ int numChars = mByteArray.length;
+ if (0 <= maxChars && maxChars < count) {
+ count = maxChars;
+ }
+
+ int end = offset + count - 1;
+ if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+ return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the String value associated with this array. Byte arrays are
+ * considered as ascii encoded strings.
+ */
+ String asMaybeCompressedString(int offset, int count, int maxChars) {
+ String str = asString(offset, count, maxChars);
+ if (str == null) {
+ str = asAsciiString(offset, count, maxChars);
+ }
+ return str;
+ }
+
+ @Override public AhatInstance getAssociatedBitmapInstance() {
+ if (mByteArray != null) {
+ List<AhatInstance> refs = getHardReverseReferences();
+ if (refs.size() == 1) {
+ AhatInstance ref = refs.get(0);
+ return ref.getAssociatedBitmapInstance();
+ }
+ }
+ return null;
+ }
+
+ @Override public String toString() {
+ String className = getClassName();
+ if (className.endsWith("[]")) {
+ className = className.substring(0, className.length() - 2);
+ }
+ return String.format("%s[%d]@%08x", className, mValues.size(), getId());
+ }
+
+ byte[] asByteArray() {
+ return mByteArray;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
new file mode 100644
index 0000000..fae34b0
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.List;
+
+public class AhatClassInstance extends AhatInstance {
+ private FieldValue[] mFieldValues;
+
+ public AhatClassInstance(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ClassInstance classInst = (ClassInstance)inst;
+ List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
+ mFieldValues = new FieldValue[fieldValues.size()];
+ for (int i = 0; i < mFieldValues.length; i++) {
+ ClassInstance.FieldValue field = fieldValues.get(i);
+ String name = field.getField().getName();
+ String type = field.getField().getType().toString();
+ Value value = snapshot.getValue(field.getValue());
+
+ mFieldValues[i] = new FieldValue(name, type, value);
+
+ if (field.getValue() instanceof Instance) {
+ Instance ref = (Instance)field.getValue();
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+ }
+ }
+ }
+ }
+
+ @Override public Value getField(String fieldName) {
+ for (FieldValue field : mFieldValues) {
+ if (fieldName.equals(field.getName())) {
+ return field.getValue();
+ }
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getRefField(String fieldName) {
+ Value value = getField(fieldName);
+ return value == null ? null : value.asAhatInstance();
+ }
+
+ /**
+ * Read an int field of an instance.
+ * The field is assumed to be an int type.
+ * Returns <code>def</code> if the field value is not an int or could not be
+ * read.
+ */
+ private Integer getIntField(String fieldName, Integer def) {
+ Value value = getField(fieldName);
+ if (value == null || !value.isInteger()) {
+ return def;
+ }
+ return value.asInteger();
+ }
+
+ /**
+ * Read a long field of this instance.
+ * The field is assumed to be a long type.
+ * Returns <code>def</code> if the field value is not an long or could not
+ * be read.
+ */
+ private Long getLongField(String fieldName, Long def) {
+ Value value = getField(fieldName);
+ if (value == null || !value.isLong()) {
+ return def;
+ }
+ return value.asLong();
+ }
+
+ /**
+ * Returns the list of class instance fields for this instance.
+ */
+ public List<FieldValue> getInstanceFields() {
+ return Arrays.asList(mFieldValues);
+ }
+
+ /**
+ * Returns true if this is an instance of a class with the given name.
+ */
+ private boolean isInstanceOfClass(String className) {
+ AhatClassObj cls = getClassObj();
+ while (cls != null) {
+ if (className.equals(cls.getName())) {
+ return true;
+ }
+ cls = cls.getSuperClassObj();
+ }
+ return false;
+ }
+
+ @Override public String asString(int maxChars) {
+ if (!isInstanceOfClass("java.lang.String")) {
+ return null;
+ }
+
+ Value value = getField("value");
+ if (!value.isAhatInstance()) {
+ return null;
+ }
+
+ AhatInstance inst = value.asAhatInstance();
+ if (inst.isArrayInstance()) {
+ AhatArrayInstance chars = inst.asArrayInstance();
+ int numChars = chars.getLength();
+ int count = getIntField("count", numChars);
+ int offset = getIntField("offset", 0);
+ return chars.asMaybeCompressedString(offset, count, maxChars);
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getReferent() {
+ if (isInstanceOfClass("java.lang.ref.Reference")) {
+ return getRefField("referent");
+ }
+ return null;
+ }
+
+ @Override public NativeAllocation getNativeAllocation() {
+ if (!isInstanceOfClass("libcore.util.NativeAllocationRegistry$CleanerThunk")) {
+ return null;
+ }
+
+ Long pointer = getLongField("nativePtr", null);
+ if (pointer == null) {
+ return null;
+ }
+
+ // Search for the registry field of inst.
+ AhatInstance registry = null;
+ for (FieldValue field : mFieldValues) {
+ Value fieldValue = field.getValue();
+ if (fieldValue.isAhatInstance()) {
+ AhatClassInstance fieldInst = fieldValue.asAhatInstance().asClassInstance();
+ if (fieldInst != null
+ && fieldInst.isInstanceOfClass("libcore.util.NativeAllocationRegistry")) {
+ registry = fieldInst;
+ break;
+ }
+ }
+ }
+
+ if (registry == null || !registry.isClassInstance()) {
+ return null;
+ }
+
+ Long size = registry.asClassInstance().getLongField("size", null);
+ if (size == null) {
+ return null;
+ }
+
+ AhatInstance referent = null;
+ for (AhatInstance ref : getHardReverseReferences()) {
+ if (ref.isClassInstance() && ref.asClassInstance().isInstanceOfClass("sun.misc.Cleaner")) {
+ referent = ref.getReferent();
+ if (referent != null) {
+ break;
+ }
+ }
+ }
+
+ if (referent == null) {
+ return null;
+ }
+ return new NativeAllocation(size, getHeap(), pointer, referent);
+ }
+
+ @Override public String getDexCacheLocation(int maxChars) {
+ if (isInstanceOfClass("java.lang.DexCache")) {
+ AhatInstance location = getRefField("location");
+ if (location != null) {
+ return location.asString(maxChars);
+ }
+ }
+ return null;
+ }
+
+ @Override public AhatInstance getAssociatedBitmapInstance() {
+ if (isInstanceOfClass("android.graphics.Bitmap")) {
+ return this;
+ }
+ return null;
+ }
+
+ @Override public boolean isClassInstance() {
+ return true;
+ }
+
+ @Override public AhatClassInstance asClassInstance() {
+ return this;
+ }
+
+ @Override public String toString() {
+ return String.format("%s@%08x", getClassName(), getId());
+ }
+
+ /**
+ * Read the given field from the given instance.
+ * The field is assumed to be a byte[] field.
+ * Returns null if the field value is null, not a byte[] or could not be read.
+ */
+ private byte[] getByteArrayField(String fieldName) {
+ Value value = getField(fieldName);
+ if (!value.isAhatInstance()) {
+ return null;
+ }
+ return value.asAhatInstance().asByteArray();
+ }
+
+ public BufferedImage asBitmap() {
+ if (!isInstanceOfClass("android.graphics.Bitmap")) {
+ return null;
+ }
+
+ Integer width = getIntField("mWidth", null);
+ if (width == null) {
+ return null;
+ }
+
+ Integer height = getIntField("mHeight", null);
+ if (height == null) {
+ return null;
+ }
+
+ byte[] buffer = getByteArrayField("mBuffer");
+ if (buffer == null) {
+ return null;
+ }
+
+ // Convert the raw data to an image
+ // Convert BGRA to ABGR
+ int[] abgr = new int[height * width];
+ for (int i = 0; i < abgr.length; i++) {
+ abgr[i] = (
+ (((int) buffer[i * 4 + 3] & 0xFF) << 24)
+ + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
+ + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
+ + ((int) buffer[i * 4 + 2] & 0xFF));
+ }
+
+ BufferedImage bitmap = new BufferedImage(
+ width, height, BufferedImage.TYPE_4BYTE_ABGR);
+ bitmap.setRGB(0, 0, width, height, abgr, 0, width);
+ return bitmap;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
new file mode 100644
index 0000000..828bbfc
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class AhatClassObj extends AhatInstance {
+ private String mClassName;
+ private AhatClassObj mSuperClassObj;
+ private AhatInstance mClassLoader;
+ private FieldValue[] mStaticFieldValues;
+
+ public AhatClassObj(long id) {
+ super(id);
+ }
+
+ @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+ super.initialize(snapshot, inst);
+
+ ClassObj classObj = (ClassObj)inst;
+ mClassName = classObj.getClassName();
+
+ ClassObj superClassObj = classObj.getSuperClassObj();
+ if (superClassObj != null) {
+ mSuperClassObj = snapshot.findClassObj(superClassObj.getId());
+ }
+
+ Instance loader = classObj.getClassLoader();
+ if (loader != null) {
+ mClassLoader = snapshot.findInstance(loader.getId());
+ }
+
+ Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet();
+ mStaticFieldValues = new FieldValue[fieldValues.size()];
+ int index = 0;
+ for (Map.Entry<Field, Object> field : fieldValues) {
+ String name = field.getKey().getName();
+ String type = field.getKey().getType().toString();
+ Value value = snapshot.getValue(field.getValue());
+ mStaticFieldValues[index++] = new FieldValue(name, type, value);
+
+ if (field.getValue() instanceof Instance) {
+ Instance ref = (Instance)field.getValue();
+ if (ref.getNextInstanceToGcRoot() == inst) {
+ value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the name of the class this is a class object for.
+ */
+ public String getName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the superclass of this class object.
+ */
+ public AhatClassObj getSuperClassObj() {
+ return mSuperClassObj;
+ }
+
+ /**
+ * Returns the class loader of this class object.
+ */
+ public AhatInstance getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * Returns the static field values for this class object.
+ */
+ public List<FieldValue> getStaticFieldValues() {
+ return Arrays.asList(mStaticFieldValues);
+ }
+
+ @Override public boolean isClassObj() {
+ return true;
+ }
+
+ @Override public AhatClassObj asClassObj() {
+ return this;
+ }
+
+ @Override public String toString() {
+ return mClassName;
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatField.java b/tools/ahat/src/heapdump/AhatField.java
new file mode 100644
index 0000000..a25ee28
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatField.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatField {
+ private final String mName;
+ private final String mType;
+
+ public AhatField(String name, String type) {
+ mName = name;
+ mType = type;
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a description of the type of the field.
+ */
+ public String getType() {
+ return mType;
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatHeap.java b/tools/ahat/src/heapdump/AhatHeap.java
new file mode 100644
index 0000000..0bc2a02
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatHeap.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatHeap {
+ private String mName;
+ private long mSize = 0;
+ private int mIndex;
+
+ AhatHeap(String name, int index) {
+ mName = name;
+ mIndex = index;
+ }
+
+ void addToSize(long increment) {
+ mSize += increment;
+ }
+
+ /**
+ * Returns a unique instance for this heap between 0 and the total number of
+ * heaps in this snapshot.
+ */
+ int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * Returns the name of this heap.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the total number of bytes allocated on this heap.
+ */
+ public long getSize() {
+ return mSize;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
new file mode 100644
index 0000000..d1730d1
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AhatInstance {
+ private long mId;
+ private long mSize;
+ private long mTotalRetainedSize;
+ private long mRetainedSizes[]; // Retained size indexed by heap index
+ private AhatHeap mHeap;
+ private AhatInstance mImmediateDominator;
+ private AhatInstance mNextInstanceToGcRoot;
+ private String mNextInstanceToGcRootField = "???";
+ private AhatClassObj mClassObj;
+ private AhatInstance[] mHardReverseReferences;
+ private AhatInstance[] mSoftReverseReferences;
+ private Site mSite;
+
+ // If this instance is a root, mRootTypes contains a set of the root types.
+ // If this instance is not a root, mRootTypes is null.
+ private List<String> mRootTypes;
+
+ // List of instances this instance immediately dominates.
+ private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
+
+ public AhatInstance(long id) {
+ mId = id;
+ }
+
+ /**
+ * Initializes this AhatInstance based on the given perflib instance.
+ * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps.
+ * There is no guarantee that the AhatInstances returned by
+ * snapshot.findInstance have been initialized yet.
+ */
+ void initialize(AhatSnapshot snapshot, Instance inst) {
+ mId = inst.getId();
+ mSize = inst.getSize();
+ mTotalRetainedSize = inst.getTotalRetainedSize();
+
+ AhatHeap[] heaps = snapshot.getHeaps();
+ mRetainedSizes = new long[heaps.length];
+ for (AhatHeap heap : heaps) {
+ mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex());
+ }
+
+ mHeap = snapshot.getHeap(inst.getHeap().getName());
+
+ Instance dom = inst.getImmediateDominator();
+ if (dom == null || dom instanceof RootObj) {
+ mImmediateDominator = null;
+ } else {
+ mImmediateDominator = snapshot.findInstance(dom.getId());
+ mImmediateDominator.mDominated.add(this);
+ }
+
+ ClassObj clsObj = inst.getClassObj();
+ if (clsObj != null) {
+ mClassObj = snapshot.findClassObj(clsObj.getId());
+ }
+
+ // A couple notes about reverse references:
+ // * perflib sometimes returns unreachable reverse references. If
+ // snapshot.findInstance returns null, it means the reverse reference is
+ // not reachable, so we filter it out.
+ // * We store the references as AhatInstance[] instead of
+ // ArrayList<AhatInstance> because it saves a lot of space and helps
+ // with performance when there are a lot of AhatInstances.
+ ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>();
+ ahatRefs = new ArrayList<AhatInstance>();
+ for (Instance ref : inst.getHardReverseReferences()) {
+ AhatInstance ahat = snapshot.findInstance(ref.getId());
+ if (ahat != null) {
+ ahatRefs.add(ahat);
+ }
+ }
+ mHardReverseReferences = new AhatInstance[ahatRefs.size()];
+ ahatRefs.toArray(mHardReverseReferences);
+
+ List<Instance> refs = inst.getSoftReverseReferences();
+ ahatRefs.clear();
+ if (refs != null) {
+ for (Instance ref : refs) {
+ AhatInstance ahat = snapshot.findInstance(ref.getId());
+ if (ahat != null) {
+ ahatRefs.add(ahat);
+ }
+ }
+ }
+ mSoftReverseReferences = new AhatInstance[ahatRefs.size()];
+ ahatRefs.toArray(mSoftReverseReferences);
+ }
+
+ /**
+ * Returns a unique identifier for the instance.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the shallow number of bytes this object takes up.
+ */
+ public long getSize() {
+ return mSize;
+ }
+
+ /**
+ * Returns the number of bytes belonging to the given heap that this instance
+ * retains.
+ */
+ public long getRetainedSize(AhatHeap heap) {
+ return mRetainedSizes[heap.getIndex()];
+ }
+
+ /**
+ * Returns the total number of bytes this instance retains.
+ */
+ public long getTotalRetainedSize() {
+ return mTotalRetainedSize;
+ }
+
+ /**
+ * Returns the heap that this instance is allocated on.
+ */
+ public AhatHeap getHeap() {
+ return mHeap;
+ }
+
+ /**
+ * Returns true if this instance is marked as a root instance.
+ */
+ public boolean isRoot() {
+ return mRootTypes != null;
+ }
+
+ /**
+ * Marks this instance as being a root of the given type.
+ */
+ void addRootType(String type) {
+ if (mRootTypes == null) {
+ mRootTypes = new ArrayList<String>();
+ mRootTypes.add(type);
+ } else if (!mRootTypes.contains(type)) {
+ mRootTypes.add(type);
+ }
+ }
+
+ /**
+ * Returns a list of string descriptions of the root types of this object.
+ * Returns null if this object is not a root.
+ */
+ public Collection<String> getRootTypes() {
+ return mRootTypes;
+ }
+
+ /**
+ * Returns the immediate dominator of this instance.
+ * Returns null if this is a root instance.
+ */
+ public AhatInstance getImmediateDominator() {
+ return mImmediateDominator;
+ }
+
+ /**
+ * Returns a list of those objects immediately dominated by the given
+ * instance.
+ */
+ public List<AhatInstance> getDominated() {
+ return mDominated;
+ }
+
+ /**
+ * Returns the site where this instance was allocated.
+ */
+ public Site getSite() {
+ return mSite;
+ }
+
+ /**
+ * Sets the allocation site of this instance.
+ */
+ void setSite(Site site) {
+ mSite = site;
+ }
+
+ /**
+ * Returns true if the given instance is a class object
+ */
+ public boolean isClassObj() {
+ // Overridden by AhatClassObj.
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatClassObj if this is an AhatClassObj.
+ * Returns null if this is not an AhatClassObj.
+ */
+ public AhatClassObj asClassObj() {
+ // Overridden by AhatClassObj.
+ return null;
+ }
+
+ /**
+ * Returns the class object instance for the class of this object.
+ */
+ public AhatClassObj getClassObj() {
+ return mClassObj;
+ }
+
+ /**
+ * Returns the name of the class this object belongs to.
+ */
+ public String getClassName() {
+ AhatClassObj classObj = getClassObj();
+ return classObj == null ? "???" : classObj.getName();
+ }
+
+ /**
+ * Returns true if the given instance is an array instance
+ */
+ public boolean isArrayInstance() {
+ // Overridden by AhatArrayInstance.
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatArrayInstance if this is an AhatArrayInstance.
+ * Returns null if this is not an AhatArrayInstance.
+ */
+ public AhatArrayInstance asArrayInstance() {
+ // Overridden by AhatArrayInstance.
+ return null;
+ }
+
+ /**
+ * Assuming this instance represents a NativeAllocation, return information
+ * about the native allocation. Returns null if the given instance does not
+ * represent a native allocation.
+ */
+ public NativeAllocation getNativeAllocation() {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Returns true if the given instance is a class instance
+ */
+ public boolean isClassInstance() {
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatClassInstance if this is an AhatClassInstance.
+ * Returns null if this is not an AhatClassInstance.
+ */
+ public AhatClassInstance asClassInstance() {
+ return null;
+ }
+
+ /**
+ * Return the referent associated with this instance.
+ * This is relevent for instances of java.lang.ref.Reference.
+ * Returns null if the instance has no referent associated with it.
+ */
+ public AhatInstance getReferent() {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Returns a list of objects with hard references to this object.
+ */
+ public List<AhatInstance> getHardReverseReferences() {
+ return Arrays.asList(mHardReverseReferences);
+ }
+
+ /**
+ * Returns a list of objects with soft references to this object.
+ */
+ public List<AhatInstance> getSoftReverseReferences() {
+ return Arrays.asList(mSoftReverseReferences);
+ }
+
+ /**
+ * Returns the value of a field of an instance.
+ * Returns null if the field value is null, the field couldn't be read, or
+ * there are multiple fields with the same name.
+ */
+ public Value getField(String fieldName) {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Reads a reference field of this instance.
+ * Returns null if the field value is null, or if the field couldn't be read.
+ */
+ public AhatInstance getRefField(String fieldName) {
+ // Overridden by AhatClassInstance.
+ return null;
+ }
+
+ /**
+ * Assuming inst represents a DexCache object, return the dex location for
+ * that dex cache. Returns null if the given instance doesn't represent a
+ * DexCache object or the location could not be found.
+ * If maxChars is non-negative, the returned location is truncated to
+ * maxChars in length.
+ */
+ public String getDexCacheLocation(int maxChars) {
+ return null;
+ }
+
+ /**
+ * Return the bitmap instance associated with this object, or null if there
+ * is none. This works for android.graphics.Bitmap instances and their
+ * underlying Byte[] instances.
+ */
+ public AhatInstance getAssociatedBitmapInstance() {
+ return null;
+ }
+
+ /**
+ * Read the string value from this instance.
+ * Returns null if this object can't be interpreted as a string.
+ * The returned string is truncated to maxChars characters.
+ * If maxChars is negative, the returned string is not truncated.
+ */
+ public String asString(int maxChars) {
+ // By default instances can't be interpreted as a string. This method is
+ // overridden by AhatClassInstance and AhatArrayInstance for those cases
+ // when an instance can be interpreted as a string.
+ return null;
+ }
+
+ /**
+ * Reads the string value from an hprof Instance.
+ * Returns null if the object can't be interpreted as a string.
+ */
+ public String asString() {
+ return asString(-1);
+ }
+
+ /**
+ * Return the bitmap associated with the given instance, if any.
+ * This is relevant for instances of android.graphics.Bitmap and byte[].
+ * Returns null if there is no bitmap associated with the given instance.
+ */
+ public BufferedImage asBitmap() {
+ return null;
+ }
+
+ /**
+ * Returns a sample path from a GC root to this instance.
+ * This instance is included as the last element of the path with an empty
+ * field description.
+ */
+ public List<PathElement> getPathFromGcRoot() {
+ List<PathElement> path = new ArrayList<PathElement>();
+
+ AhatInstance dom = this;
+ for (PathElement elem = new PathElement(this, ""); elem != null;
+ elem = getNextPathElementToGcRoot(elem.instance)) {
+ if (elem.instance.equals(dom)) {
+ elem.isDominator = true;
+ dom = dom.getImmediateDominator();
+ }
+ path.add(elem);
+ }
+ Collections.reverse(path);
+ return path;
+ }
+
+ /**
+ * Returns the next instance to GC root from this object and a string
+ * description of which field of that object refers to the given instance.
+ * Returns null if the given instance has no next instance to the gc root.
+ */
+ private static PathElement getNextPathElementToGcRoot(AhatInstance inst) {
+ AhatInstance parent = inst.mNextInstanceToGcRoot;
+ if (parent == null) {
+ return null;
+ }
+ return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
+ }
+
+ void setNextInstanceToGcRoot(AhatInstance inst, String field) {
+ mNextInstanceToGcRoot = inst;
+ mNextInstanceToGcRootField = field;
+ }
+
+ /** Returns a human-readable identifier for this object.
+ * For class objects, the string is the class name.
+ * For class instances, the string is the class name followed by '@' and the
+ * hex id of the instance.
+ * For array instances, the string is the array type followed by the size in
+ * square brackets, followed by '@' and the hex id of the instance.
+ */
+ @Override public abstract String toString();
+
+ /**
+ * Read the byte[] value from an hprof Instance.
+ * Returns null if the instance is not a byte array.
+ */
+ byte[] asByteArray() {
+ return null;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
new file mode 100644
index 0000000..400f093
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.StackFrame;
+import com.android.tools.perflib.heap.StackTrace;
+import gnu.trove.TObjectProcedure;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AhatSnapshot {
+ private final Site mRootSite = new Site("ROOT");
+
+ // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
+ private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>();
+
+ // List of all ahat instances stored in increasing order by id.
+ private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
+
+ // Map from class name to class object.
+ private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>();
+
+ private final AhatHeap[] mHeaps;
+
+ private final List<NativeAllocation> mNativeAllocations = new ArrayList<NativeAllocation>();
+
+ /**
+ * Create an AhatSnapshot from an hprof file.
+ */
+ public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
+ return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map);
+ }
+
+ /**
+ * Create an AhatSnapshot from an in-memory data buffer.
+ */
+ public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException {
+ AhatSnapshot snapshot = new AhatSnapshot(buffer, map);
+
+ // Request a GC now to clean up memory used by perflib. This helps to
+ // avoid a noticable pause when visiting the first interesting page in
+ // ahat.
+ System.gc();
+
+ return snapshot;
+ }
+
+ /**
+ * Constructs an AhatSnapshot for the given hprof binary data.
+ */
+ private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
+ Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
+ snapshot.computeDominators();
+
+ // Properly label the class of class objects in the perflib snapshot, and
+ // count the total number of instances.
+ final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
+ if (javaLangClass != null) {
+ for (Heap heap : snapshot.getHeaps()) {
+ Collection<ClassObj> classes = heap.getClasses();
+ for (ClassObj clsObj : classes) {
+ if (clsObj.getClassObj() == null) {
+ clsObj.setClassId(javaLangClass.getId());
+ }
+ }
+ }
+ }
+
+ // Create mappings from id to ahat instance and heaps.
+ Collection<Heap> heaps = snapshot.getHeaps();
+ mHeaps = new AhatHeap[heaps.size()];
+ for (Heap heap : heaps) {
+ int heapIndex = snapshot.getHeapIndex(heap);
+ mHeaps[heapIndex] = new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap));
+ TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance inst) {
+ if (inst.isReachable()) {
+ long id = inst.getId();
+ if (inst instanceof ClassInstance) {
+ mInstances.add(new AhatClassInstance(id));
+ } else if (inst instanceof ArrayInstance) {
+ mInstances.add(new AhatArrayInstance(id));
+ } else if (inst instanceof ClassObj) {
+ AhatClassObj classObj = new AhatClassObj(id);
+ mInstances.add(classObj);
+ mClasses.put(((ClassObj)inst).getClassName(), classObj);
+ }
+ }
+ return true;
+ }
+ };
+ for (Instance instance : heap.getClasses()) {
+ doCreate.execute(instance);
+ }
+ heap.forEachInstance(doCreate);
+ }
+
+ // Sort the instances by id so we can use binary search to lookup
+ // instances by id.
+ mInstances.sort(new Comparator<AhatInstance>() {
+ @Override
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(a.getId(), b.getId());
+ }
+ });
+
+ // Initialize ahat snapshot and instances based on the perflib snapshot
+ // and instances.
+ for (AhatInstance ahat : mInstances) {
+ Instance inst = snapshot.findInstance(ahat.getId());
+ ahat.initialize(this, inst);
+
+ if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) {
+ mRooted.add(ahat);
+ }
+
+ ahat.getHeap().addToSize(ahat.getSize());
+
+ // Update sites.
+ StackFrame[] frames = null;
+ StackTrace stack = inst.getStack();
+ if (stack != null) {
+ frames = stack.getFrames();
+ }
+ Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
+ ahat.setSite(site);
+ }
+
+ // Record the roots and their types.
+ for (RootObj root : snapshot.getGCRoots()) {
+ Instance inst = root.getReferredInstance();
+ if (inst != null) {
+ findInstance(inst.getId()).addRootType(root.getRootType().toString());
+ }
+ }
+ snapshot.dispose();
+
+ // Update the native allocations.
+ for (AhatInstance ahat : mInstances) {
+ NativeAllocation alloc = ahat.getNativeAllocation();
+ if (alloc != null) {
+ mNativeAllocations.add(alloc);
+ }
+ }
+ }
+
+ /**
+ * Returns the instance with given id in this snapshot.
+ * Returns null if no instance with the given id is found.
+ */
+ public AhatInstance findInstance(long id) {
+ // Binary search over the sorted instances.
+ int start = 0;
+ int end = mInstances.size();
+ while (start < end) {
+ int mid = start + ((end - start) / 2);
+ AhatInstance midInst = mInstances.get(mid);
+ long midId = midInst.getId();
+ if (id == midId) {
+ return midInst;
+ } else if (id < midId) {
+ end = mid;
+ } else {
+ start = mid + 1;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the AhatClassObj with given id in this snapshot.
+ * Returns null if no class object with the given id is found.
+ */
+ public AhatClassObj findClassObj(long id) {
+ AhatInstance inst = findInstance(id);
+ return inst == null ? null : inst.asClassObj();
+ }
+
+ /**
+ * Returns the class object for the class with given name.
+ * Returns null if there is no class object for the given name.
+ * Note: This method is exposed for testing purposes.
+ */
+ public AhatClassObj findClass(String name) {
+ return mClasses.get(name);
+ }
+
+ /**
+ * Returns the heap with the given name, if any.
+ * Returns null if no heap with the given name could be found.
+ */
+ public AhatHeap getHeap(String name) {
+ // We expect a small number of heaps (maybe 3 or 4 total), so a linear
+ // search should be acceptable here performance wise.
+ for (AhatHeap heap : getHeaps()) {
+ if (heap.getName().equals(name)) {
+ return heap;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of heaps in the snapshot in canonical order.
+ */
+ public AhatHeap[] getHeaps() {
+ return mHeaps;
+ }
+
+ /**
+ * Returns a collection of instances whose immediate dominator is the
+ * SENTINEL_ROOT.
+ */
+ public List<AhatInstance> getRooted() {
+ return mRooted;
+ }
+
+ /**
+ * Returns a list of native allocations identified in the heap dump.
+ */
+ public List<NativeAllocation> getNativeAllocations() {
+ return mNativeAllocations;
+ }
+
+ // Get the site associated with the given id and depth.
+ // Returns the root site if no such site found.
+ public Site getSite(int id, int depth) {
+ AhatInstance obj = findInstance(id);
+ if (obj == null) {
+ return mRootSite;
+ }
+
+ Site site = obj.getSite();
+ for (int i = 0; i < depth && site.getParent() != null; i++) {
+ site = site.getParent();
+ }
+ return site;
+ }
+
+ // Return the Value for the given perflib value object.
+ Value getValue(Object value) {
+ if (value instanceof Instance) {
+ value = findInstance(((Instance)value).getId());
+ }
+ return value == null ? null : new Value(value);
+ }
+}
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
new file mode 100644
index 0000000..dd9cb07
--- /dev/null
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class FieldValue {
+ private final String mName;
+ private final String mType;
+ private final Value mValue;
+
+ public FieldValue(String name, String type, Value value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ }
+
+ /**
+ * Returns the name of the field.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a description of the type of the field.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the value of this field.
+ */
+ public Value getValue() {
+ return mValue;
+ }
+}
diff --git a/tools/ahat/src/heapdump/NativeAllocation.java b/tools/ahat/src/heapdump/NativeAllocation.java
new file mode 100644
index 0000000..5188f44
--- /dev/null
+++ b/tools/ahat/src/heapdump/NativeAllocation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class NativeAllocation {
+ public long size;
+ public AhatHeap heap;
+ public long pointer;
+ public AhatInstance referent;
+
+ public NativeAllocation(long size, AhatHeap heap, long pointer, AhatInstance referent) {
+ this.size = size;
+ this.heap = heap;
+ this.pointer = pointer;
+ this.referent = referent;
+ }
+}
diff --git a/tools/ahat/src/heapdump/PathElement.java b/tools/ahat/src/heapdump/PathElement.java
new file mode 100644
index 0000000..bbae59e
--- /dev/null
+++ b/tools/ahat/src/heapdump/PathElement.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class PathElement {
+ public final AhatInstance instance;
+ public final String field;
+ public boolean isDominator;
+
+ public PathElement(AhatInstance instance, String field) {
+ this.instance = instance;
+ this.field = field;
+ this.isDominator = false;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
new file mode 100644
index 0000000..97cbf18
--- /dev/null
+++ b/tools/ahat/src/heapdump/Site.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.StackFrame;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Site {
+ // The site that this site was directly called from.
+ // mParent is null for the root site.
+ private Site mParent;
+
+ private String mMethodName;
+ private String mSignature;
+ private String mFilename;
+ private int mLineNumber;
+
+ // To identify this site, we pick a stack trace that includes the site.
+ // mId is the id of an object allocated at that stack trace, and mDepth
+ // is the number of calls between this site and the innermost site of
+ // allocation of the object with mId.
+ // For the root site, mId is 0 and mDepth is 0.
+ private long mId;
+ private int mDepth;
+
+ // The total size of objects allocated in this site (including child sites),
+ // organized by heap index. Heap indices outside the range of mSizesByHeap
+ // implicitly have size 0.
+ private long[] mSizesByHeap;
+
+ // List of child sites.
+ private List<Site> mChildren;
+
+ // List of all objects allocated in this site (including child sites).
+ private List<AhatInstance> mObjects;
+ private List<ObjectsInfo> mObjectsInfos;
+ private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;
+
+ public static class ObjectsInfo {
+ public AhatHeap heap;
+ public AhatClassObj classObj;
+ public long numInstances;
+ public long numBytes;
+
+ public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
+ this.heap = heap;
+ this.classObj = classObj;
+ this.numInstances = numInstances;
+ this.numBytes = numBytes;
+ }
+
+ /**
+ * Returns the name of the class this ObjectsInfo is associated with.
+ */
+ public String getClassName() {
+ return classObj == null ? "???" : classObj.getName();
+ }
+ }
+
+ /**
+ * Construct a root site.
+ */
+ public Site(String name) {
+ this(null, name, "", "", 0, 0, 0);
+ }
+
+ public Site(Site parent, String method, String signature, String file,
+ int line, long id, int depth) {
+ mParent = parent;
+ mMethodName = method;
+ mSignature = signature;
+ mFilename = file;
+ mLineNumber = line;
+ mId = id;
+ mDepth = depth;
+ mSizesByHeap = new long[1];
+ mChildren = new ArrayList<Site>();
+ mObjects = new ArrayList<AhatInstance>();
+ mObjectsInfos = new ArrayList<ObjectsInfo>();
+ mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>();
+ }
+
+ /**
+ * Add an instance to this site.
+ * Returns the site at which the instance was allocated.
+ * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
+ * @param depth - The number of frames remaining before the inner-most frame is reached.
+ */
+ Site add(StackFrame[] frames, int depth, AhatInstance inst) {
+ return add(this, frames, depth, inst);
+ }
+
+ private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
+ while (true) {
+ site.mObjects.add(inst);
+
+ AhatHeap heap = inst.getHeap();
+ if (heap.getIndex() >= site.mSizesByHeap.length) {
+ long[] newSizes = new long[heap.getIndex() + 1];
+ for (int i = 0; i < site.mSizesByHeap.length; i++) {
+ newSizes[i] = site.mSizesByHeap[i];
+ }
+ site.mSizesByHeap = newSizes;
+ }
+ site.mSizesByHeap[heap.getIndex()] += inst.getSize();
+
+ Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = site.mObjectsInfoMap.get(inst.getHeap());
+ if (classToObjectsInfo == null) {
+ classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
+ site.mObjectsInfoMap.put(inst.getHeap(), classToObjectsInfo);
+ }
+
+ ObjectsInfo info = classToObjectsInfo.get(inst.getClassObj());
+ if (info == null) {
+ info = new ObjectsInfo(inst.getHeap(), inst.getClassObj(), 0, 0);
+ site.mObjectsInfos.add(info);
+ classToObjectsInfo.put(inst.getClassObj(), info);
+ }
+
+ info.numInstances++;
+ info.numBytes += inst.getSize();
+
+ if (depth > 0) {
+ StackFrame next = frames[depth - 1];
+ Site child = null;
+ for (int i = 0; i < site.mChildren.size(); i++) {
+ Site curr = site.mChildren.get(i);
+ if (curr.mLineNumber == next.getLineNumber()
+ && curr.mMethodName.equals(next.getMethodName())
+ && curr.mSignature.equals(next.getSignature())
+ && curr.mFilename.equals(next.getFilename())) {
+ child = curr;
+ break;
+ }
+ }
+ if (child == null) {
+ child = new Site(site, next.getMethodName(), next.getSignature(),
+ next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
+ site.mChildren.add(child);
+ }
+ depth = depth - 1;
+ site = child;
+ } else {
+ return site;
+ }
+ }
+ }
+
+ // Get the size of a site for a specific heap.
+ public long getSize(AhatHeap heap) {
+ int index = heap.getIndex();
+ return index < mSizesByHeap.length ? mSizesByHeap[index] : 0;
+ }
+
+ /**
+ * Get the list of objects allocated under this site. Includes objects
+ * allocated in children sites.
+ */
+ public Collection<AhatInstance> getObjects() {
+ return mObjects;
+ }
+
+ public List<ObjectsInfo> getObjectsInfos() {
+ return mObjectsInfos;
+ }
+
+ // Get the combined size of the site for all heaps.
+ public long getTotalSize() {
+ long total = 0;
+ for (int i = 0; i < mSizesByHeap.length; i++) {
+ total += mSizesByHeap[i];
+ }
+ return total;
+ }
+
+ /**
+ * Return the site this site was called from.
+ * Returns null for the root site.
+ */
+ public Site getParent() {
+ return mParent;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getSignature() {
+ return mSignature;
+ }
+
+ public String getFilename() {
+ return mFilename;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+
+ /**
+ * Returns the id of some object allocated in this site.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the number of frames between this site and the site where the
+ * object with id getId() was allocated.
+ */
+ public int getDepth() {
+ return mDepth;
+ }
+
+ public List<Site> getChildren() {
+ return mChildren;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
new file mode 100644
index 0000000..e2bdc71
--- /dev/null
+++ b/tools/ahat/src/heapdump/Value.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Value represents a field value in a heap dump. The field value is either a
+ * subclass of AhatInstance or a primitive Java type.
+ */
+public class Value {
+ private Object mObject;
+
+ /**
+ * Constructs a value from a generic Java Object.
+ * The Object must either be a boxed Java primitive type or a subclass of
+ * AhatInstance. The object must not be null.
+ */
+ Value(Object object) {
+ // TODO: Check that the Object is either an AhatSnapshot or boxed Java
+ // primitive type?
+ assert object != null;
+ mObject = object;
+ }
+
+ /**
+ * Returns true if the Value is an AhatInstance, as opposed to a Java
+ * primitive value.
+ */
+ public boolean isAhatInstance() {
+ return mObject instanceof AhatInstance;
+ }
+
+ /**
+ * Return the Value as an AhatInstance if it is one.
+ * Returns null if the Value represents a Java primitive value.
+ */
+ public AhatInstance asAhatInstance() {
+ if (isAhatInstance()) {
+ return (AhatInstance)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the Value is an Integer.
+ */
+ public boolean isInteger() {
+ return mObject instanceof Integer;
+ }
+
+ /**
+ * Return the Value as an Integer if it is one.
+ * Returns null if the Value does not represent an Integer.
+ */
+ public Integer asInteger() {
+ if (isInteger()) {
+ return (Integer)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the Value is an Long.
+ */
+ public boolean isLong() {
+ return mObject instanceof Long;
+ }
+
+ /**
+ * Return the Value as an Long if it is one.
+ * Returns null if the Value does not represent an Long.
+ */
+ public Long asLong() {
+ if (isLong()) {
+ return (Long)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Return the Value as a Byte if it is one.
+ * Returns null if the Value does not represent a Byte.
+ */
+ public Byte asByte() {
+ if (mObject instanceof Byte) {
+ return (Byte)mObject;
+ }
+ return null;
+ }
+
+ /**
+ * Return the Value as a Char if it is one.
+ * Returns null if the Value does not represent a Char.
+ */
+ public Character asChar() {
+ if (mObject instanceof Character) {
+ return (Character)mObject;
+ }
+ return null;
+ }
+
+ public String toString() {
+ return mObject.toString();
+ }
+}
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 587d9de..e0b3da7 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -20,6 +20,7 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import libcore.util.NativeAllocationRegistry;
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
/**
* Program used to create a heap dump for test purposes.
@@ -45,7 +46,7 @@
// class and reading the desired field.
public static class DumpedStuff {
public String basicString = "hello, world";
- public String nonAscii = "Sigma (\u01a9) is not ASCII";
+ public String nonAscii = "Sigma (Æ©) is not ASCII";
public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes.
public char[] charArray = "char thing".toCharArray();
public String nullString = null;
@@ -53,12 +54,14 @@
public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
+ public WeakReference aNullReferentReference = new WeakReference(null, referenceQueue);
public byte[] bigArray;
public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
new ObjectTree(
new ObjectTree(null, new ObjectTree(null, null)),
new ObjectTree(null, null)),
null};
+ public Object[] basicStringRef;
DumpedStuff() {
int N = 1000000;
@@ -82,9 +85,18 @@
}
String file = args[0];
+ // Enable allocation tracking so we get stack traces in the heap dump.
+ DdmVmInternal.enableRecentAllocations(true);
+
// Allocate the instance of DumpedStuff.
stuff = new DumpedStuff();
+ // Create a bunch of unreachable objects pointing to basicString for the
+ // reverseReferencesAreNotUnreachable test
+ for (int i = 0; i < 100; i++) {
+ stuff.basicStringRef = new Object[]{stuff.basicString};
+ }
+
// Take a heap dump that will include that instance of DumpedStuff.
System.err.println("Dumping hprof data to " + file);
VMDebug.dumpHprofData(file);
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
new file mode 100644
index 0000000..7173b11
--- /dev/null
+++ b/tools/ahat/test/InstanceTest.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Value;
+import com.android.tools.perflib.heap.hprof.HprofClassDump;
+import com.android.tools.perflib.heap.hprof.HprofConstant;
+import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
+import com.android.tools.perflib.heap.hprof.HprofHeapDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceField;
+import com.android.tools.perflib.heap.hprof.HprofLoadClass;
+import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
+import com.android.tools.perflib.heap.hprof.HprofStaticField;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.hprof.HprofType;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class InstanceTest {
+ @Test
+ public void asStringBasic() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString());
+ }
+
+ @Test
+ public void asStringNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString());
+ }
+
+ @Test
+ public void asStringEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString());
+ }
+
+ @Test
+ public void asStringCharArray() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString());
+ }
+
+ @Test
+ public void asStringTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello", str.asString(5));
+ }
+
+ @Test
+ public void asStringTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©)", str.asString(9));
+ }
+
+ @Test
+ public void asStringTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embed", str.asString(5));
+ }
+
+ @Test
+ public void asStringCharArrayTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char ", str.asString(5));
+ }
+
+ @Test
+ public void asStringExactMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(12));
+ }
+
+ @Test
+ public void asStringExactMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(22));
+ }
+
+ @Test
+ public void asStringExactMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(12));
+ }
+
+ @Test
+ public void asStringCharArrayExactMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(10));
+ }
+
+ @Test
+ public void asStringNotTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(50));
+ }
+
+ @Test
+ public void asStringNotTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(50));
+ }
+
+ @Test
+ public void asStringNotTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(50));
+ }
+
+ @Test
+ public void asStringCharArrayNotTruncated() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(50));
+ }
+
+ @Test
+ public void asStringNegativeMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("basicString");
+ assertEquals("hello, world", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNegativeMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+ assertEquals("Sigma (Æ©) is not ASCII", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNegativeMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+ assertEquals("embedded\0...", str.asString(-3));
+ }
+
+ @Test
+ public void asStringCharArrayNegativeMax() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance str = dump.getDumpedAhatInstance("charArray");
+ assertEquals("char thing", str.asString(-3));
+ }
+
+ @Test
+ public void asStringNull() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("nullString");
+ assertNull(obj);
+ }
+
+ @Test
+ public void asStringNotString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertNotNull(obj);
+ assertNull(obj.asString());
+ }
+
+ @Test
+ public void basicReference() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference");
+ AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference");
+ AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference");
+ AhatInstance referent = dump.getDumpedAhatInstance("anObject");
+ assertNotNull(pref);
+ assertNotNull(wref);
+ assertNotNull(nref);
+ assertNotNull(referent);
+ assertEquals(referent, pref.getReferent());
+ assertEquals(referent, wref.getReferent());
+ assertNull(nref.getReferent());
+ assertNull(referent.getReferent());
+ }
+
+ @Test
+ public void gcRootPath() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
+ AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
+ Value value = gcPathArray.asArrayInstance().getValue(2);
+ AhatInstance base = value.asAhatInstance();
+ AhatInstance left = base.getRefField("left");
+ AhatInstance right = base.getRefField("right");
+ AhatInstance target = left.getRefField("right");
+
+ List<PathElement> path = target.getPathFromGcRoot();
+ assertEquals(6, path.size());
+
+ assertEquals(main, path.get(0).instance);
+ assertEquals(".stuff", path.get(0).field);
+ assertTrue(path.get(0).isDominator);
+
+ assertEquals(".gcPathArray", path.get(1).field);
+ assertTrue(path.get(1).isDominator);
+
+ assertEquals(gcPathArray, path.get(2).instance);
+ assertEquals("[2]", path.get(2).field);
+ assertTrue(path.get(2).isDominator);
+
+ assertEquals(base, path.get(3).instance);
+ assertTrue(path.get(3).isDominator);
+
+ // There are two possible paths. Either it can go through the 'left' node,
+ // or the 'right' node.
+ if (path.get(3).field.equals(".left")) {
+ assertEquals(".left", path.get(3).field);
+
+ assertEquals(left, path.get(4).instance);
+ assertEquals(".right", path.get(4).field);
+ assertFalse(path.get(4).isDominator);
+
+ } else {
+ assertEquals(".right", path.get(3).field);
+
+ assertEquals(right, path.get(4).instance);
+ assertEquals(".left", path.get(4).field);
+ assertFalse(path.get(4).isDominator);
+ }
+
+ assertEquals(target, path.get(5).instance);
+ assertEquals("", path.get(5).field);
+ assertTrue(path.get(5).isDominator);
+ }
+
+ @Test
+ public void retainedSize() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ // anObject should not be an immediate dominator of any other object. This
+ // means its retained size should be equal to its size for the heap it was
+ // allocated on, and should be 0 for all other heaps.
+ AhatInstance anObject = dump.getDumpedAhatInstance("anObject");
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ long size = anObject.getSize();
+ assertEquals(size, anObject.getTotalRetainedSize());
+ assertEquals(size, anObject.getRetainedSize(anObject.getHeap()));
+ for (AhatHeap heap : snapshot.getHeaps()) {
+ if (!heap.equals(anObject.getHeap())) {
+ assertEquals(String.format("For heap '%s'", heap.getName()),
+ 0, anObject.getRetainedSize(heap));
+ }
+ }
+ }
+
+ @Test
+ public void objectNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void arrayNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void classObjNotABitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ assertNull(obj.asBitmap());
+ }
+
+ @Test
+ public void classInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference");
+ long id = obj.getId();
+ assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString());
+ }
+
+ @Test
+ public void classObjToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ assertEquals("Main", obj.toString());
+ }
+
+ @Test
+ public void arrayInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+ long id = obj.getId();
+
+ // There's a bug in perfib's proguard deobfuscation for arrays.
+ // To work around that bug for the time being, only test the suffix of
+ // the toString result. Ideally we test for string equality against
+ // "Main$ObjectTree[4]@%08x", id.
+ assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id)));
+ }
+
+ @Test
+ public void primArrayInstanceToString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("bigArray");
+ long id = obj.getId();
+ assertEquals(String.format("byte[1000000]@%08x", id), obj.toString());
+ }
+
+ @Test
+ public void isNotRoot() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ assertFalse(obj.isRoot());
+ assertNull(obj.getRootTypes());
+ }
+
+ @Test
+ public void reverseReferencesAreNotUnreachable() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("basicString");
+ assertEquals(2, obj.getHardReverseReferences().size());
+ assertEquals(0, obj.getSoftReverseReferences().size());
+ }
+
+ @Test
+ public void asStringEmbedded() throws IOException {
+ // Set up a heap dump with an instance of java.lang.String of
+ // "hello" with instance id 0x42 that is backed by a char array that is
+ // bigger. This is how ART used to represent strings, and we should still
+ // support it in case the heap dump is from a previous platform version.
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+ final int stringClassObjectId = 1;
+ records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
+ dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[]{
+ new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
+ new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+
+ dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
+ new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ values.writeInt(5); // count
+ values.writeInt(0); // hashCode
+ values.writeInt(4); // offset
+ values.writeInt(0x41); // value
+ dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
+ dump.add(new HprofRootDebugger(stringClassObjectId));
+ dump.add(new HprofRootDebugger(0x42));
+
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+ AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
+ AhatInstance chars = snapshot.findInstance(0x41);
+ assertNotNull(chars);
+ assertEquals("not helloop", chars.asString());
+
+ AhatInstance stringInstance = snapshot.findInstance(0x42);
+ assertNotNull(stringInstance);
+ assertEquals("hello", stringInstance.asString());
+ }
+}
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
deleted file mode 100644
index fe2706d..0000000
--- a/tools/ahat/test/InstanceUtilsTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
-import java.io.IOException;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-
-public class InstanceUtilsTest {
- @Test
- public void asStringBasic() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringCharArray() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str));
- }
-
- @Test
- public void asStringTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringTruncatedNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9)", InstanceUtils.asString(str, 9));
- }
-
- @Test
- public void asStringTruncatedEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embed", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringCharArrayTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char ", InstanceUtils.asString(str, 5));
- }
-
- @Test
- public void asStringExactMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, 12));
- }
-
- @Test
- public void asStringExactMaxNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 22));
- }
-
- @Test
- public void asStringExactMaxEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, 12));
- }
-
- @Test
- public void asStringCharArrayExactMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, 10));
- }
-
- @Test
- public void asStringNotTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNotTruncatedNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNotTruncatedEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringCharArrayNotTruncated() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, 50));
- }
-
- @Test
- public void asStringNegativeMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("basicString");
- assertEquals("hello, world", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNegativeMaxNonAscii() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("nonAscii");
- assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNegativeMaxEmbeddedZero() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("embeddedZero");
- assertEquals("embedded\0...", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringCharArrayNegativeMax() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance str = (Instance)dump.getDumpedThing("charArray");
- assertEquals("char thing", InstanceUtils.asString(str, -3));
- }
-
- @Test
- public void asStringNull() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance obj = (Instance)dump.getDumpedThing("nullString");
- assertNull(InstanceUtils.asString(obj));
- }
-
- @Test
- public void asStringNotString() throws IOException {
- TestDump dump = TestDump.getTestDump();
- Instance obj = (Instance)dump.getDumpedThing("anObject");
- assertNotNull(obj);
- assertNull(InstanceUtils.asString(obj));
- }
-
- @Test
- public void basicReference() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- Instance pref = (Instance)dump.getDumpedThing("aPhantomReference");
- Instance wref = (Instance)dump.getDumpedThing("aWeakReference");
- Instance referent = (Instance)dump.getDumpedThing("anObject");
- assertNotNull(pref);
- assertNotNull(wref);
- assertNotNull(referent);
- assertEquals(referent, InstanceUtils.getReferent(pref));
- assertEquals(referent, InstanceUtils.getReferent(wref));
- assertNull(InstanceUtils.getReferent(referent));
- }
-
- @Test
- public void gcRootPath() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- ClassObj main = dump.getAhatSnapshot().findClass("Main");
- ArrayInstance gcPathArray = (ArrayInstance)dump.getDumpedThing("gcPathArray");
- Object[] values = gcPathArray.getValues();
- Instance base = (Instance)values[2];
- Instance left = InstanceUtils.getRefField(base, "left");
- Instance right = InstanceUtils.getRefField(base, "right");
- Instance target = InstanceUtils.getRefField(left, "right");
-
- List<InstanceUtils.PathElement> path = InstanceUtils.getPathFromGcRoot(target);
- assertEquals(6, path.size());
-
- assertEquals(main, path.get(0).instance);
- assertEquals(".stuff", path.get(0).field);
- assertTrue(path.get(0).isDominator);
-
- assertEquals(".gcPathArray", path.get(1).field);
- assertTrue(path.get(1).isDominator);
-
- assertEquals(gcPathArray, path.get(2).instance);
- assertEquals("[2]", path.get(2).field);
- assertTrue(path.get(2).isDominator);
-
- assertEquals(base, path.get(3).instance);
- assertTrue(path.get(3).isDominator);
-
- // There are two possible paths. Either it can go through the 'left' node,
- // or the 'right' node.
- if (path.get(3).field.equals(".left")) {
- assertEquals(".left", path.get(3).field);
-
- assertEquals(left, path.get(4).instance);
- assertEquals(".right", path.get(4).field);
- assertFalse(path.get(4).isDominator);
-
- } else {
- assertEquals(".right", path.get(3).field);
-
- assertEquals(right, path.get(4).instance);
- assertEquals(".left", path.get(4).field);
- assertFalse(path.get(4).isDominator);
- }
-
- assertEquals(target, path.get(5).instance);
- assertEquals("", path.get(5).field);
- assertTrue(path.get(5).isDominator);
- }
-}
diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java
index 7ad4c1d..9babab9 100644
--- a/tools/ahat/test/NativeAllocationTest.java
+++ b/tools/ahat/test/NativeAllocationTest.java
@@ -16,12 +16,15 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.NativeAllocation;
import java.io.IOException;
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertEquals;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
public class NativeAllocationTest {
@Test
@@ -29,9 +32,9 @@
TestDump dump = TestDump.getTestDump();
AhatSnapshot snapshot = dump.getAhatSnapshot();
- Instance referent = (Instance)dump.getDumpedThing("anObject");
- for (InstanceUtils.NativeAllocation alloc : snapshot.getNativeAllocations()) {
- if (alloc.referent == referent) {
+ AhatInstance referent = dump.getDumpedAhatInstance("anObject");
+ for (NativeAllocation alloc : snapshot.getNativeAllocations()) {
+ if (alloc.referent.equals(referent)) {
assertEquals(42 , alloc.size);
assertEquals(referent.getHeap(), alloc.heap);
assertEquals(0xABCDABCD , alloc.pointer);
diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java
new file mode 100644
index 0000000..cd0ba23
--- /dev/null
+++ b/tools/ahat/test/ObjectHandlerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class ObjectHandlerTest {
+ @Test
+ public void noCrashClassInstance() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance object = dump.getDumpedAhatInstance("aPhantomReference");
+ assertNotNull(object);
+
+ AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashClassObj() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ AhatInstance object = snapshot.findClass("Main");
+ assertNotNull(object);
+
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashSystemClassObj() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ AhatInstance object = snapshot.findClass("java.lang.String");
+ assertNotNull(object);
+
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+
+ @Test
+ public void noCrashArrayInstance() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance object = dump.getDumpedAhatInstance("gcPathArray");
+ assertNotNull(object);
+
+ AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+ }
+}
diff --git a/tools/ahat/test/OverviewHandlerTest.java b/tools/ahat/test/OverviewHandlerTest.java
new file mode 100644
index 0000000..a46bfce
--- /dev/null
+++ b/tools/ahat/test/OverviewHandlerTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+
+public class OverviewHandlerTest {
+
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new OverviewHandler(snapshot, new File("my.hprof.file"));
+ TestHandler.testNoCrash(handler, "http://localhost:7100");
+ }
+}
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
index 6e46800..e13974b 100644
--- a/tools/ahat/test/PerformanceTest.java
+++ b/tools/ahat/test/PerformanceTest.java
@@ -16,13 +16,15 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
+import org.junit.Test;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import org.junit.Test;
public class PerformanceTest {
private static class NullOutputStream extends OutputStream {
@@ -36,7 +38,7 @@
// for any object, including big arrays.
TestDump dump = TestDump.getTestDump();
- Instance bigArray = (Instance)dump.getDumpedThing("bigArray");
+ AhatInstance bigArray = dump.getDumpedAhatInstance("bigArray");
assertNotNull(bigArray);
AhatSnapshot snapshot = dump.getAhatSnapshot();
diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/test/QueryTest.java
index 40e3322..5bcf8ea 100644
--- a/tools/ahat/test/QueryTest.java
+++ b/tools/ahat/test/QueryTest.java
@@ -18,9 +18,10 @@
import java.net.URI;
import java.net.URISyntaxException;
-import static org.junit.Assert.assertEquals;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
public class QueryTest {
@Test
public void simple() throws URISyntaxException {
diff --git a/tools/ahat/test/RootedHandlerTest.java b/tools/ahat/test/RootedHandlerTest.java
new file mode 100644
index 0000000..f325b8e
--- /dev/null
+++ b/tools/ahat/test/RootedHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class RootedHandlerTest {
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new RootedHandler(snapshot);
+ TestHandler.testNoCrash(handler, "http://localhost:7100/rooted");
+ }
+}
diff --git a/tools/ahat/test/SiteHandlerTest.java b/tools/ahat/test/SiteHandlerTest.java
new file mode 100644
index 0000000..37596be
--- /dev/null
+++ b/tools/ahat/test/SiteHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class SiteHandlerTest {
+ @Test
+ public void noCrash() throws IOException {
+ AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+ AhatHandler handler = new SiteHandler(snapshot);
+ TestHandler.testNoCrash(handler, "http://localhost:7100/sites");
+ }
+}
diff --git a/tools/ahat/test/SnapshotBuilder.java b/tools/ahat/test/SnapshotBuilder.java
new file mode 100644
index 0000000..0eea635
--- /dev/null
+++ b/tools/ahat/test/SnapshotBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.hprof.Hprof;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Class with utilities to help constructing snapshots for tests.
+ */
+public class SnapshotBuilder {
+
+ // Helper function to make a snapshot with id size 4 given an
+ // HprofStringBuilder and list of HprofRecords
+ public static AhatSnapshot makeSnapshot(HprofStringBuilder strings, List<HprofRecord> records)
+ throws IOException {
+ // TODO: When perflib can handle the case where strings are referred to
+ // before they are defined, just add the string records to the records
+ // list.
+ List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+ actualRecords.addAll(strings.getStringRecords());
+ actualRecords.addAll(records);
+
+ Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 4, new Date(), actualRecords);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ hprof.write(os);
+ InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+ return AhatSnapshot.fromDataBuffer(buffer, new ProguardMap());
+ }
+}
diff --git a/tools/ahat/test/SortTest.java b/tools/ahat/test/SortTest.java
deleted file mode 100644
index 02ff7db..0000000
--- a/tools/ahat/test/SortTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class SortTest {
- @Test
- public void objectsInfo() {
- Heap heapA = new Heap(0xA, "A");
- Heap heapB = new Heap(0xB, "B");
- ClassObj classA = new ClassObj(0x1A, null, "classA", 0);
- ClassObj classB = new ClassObj(0x1B, null, "classB", 0);
- ClassObj classC = new ClassObj(0x1C, null, "classC", 0);
- Site.ObjectsInfo infoA = new Site.ObjectsInfo(heapA, classA, 4, 14);
- Site.ObjectsInfo infoB = new Site.ObjectsInfo(heapB, classB, 2, 15);
- Site.ObjectsInfo infoC = new Site.ObjectsInfo(heapA, classC, 3, 13);
- Site.ObjectsInfo infoD = new Site.ObjectsInfo(heapB, classA, 5, 12);
- Site.ObjectsInfo infoE = new Site.ObjectsInfo(heapA, classB, 1, 11);
- List<Site.ObjectsInfo> list = new ArrayList<Site.ObjectsInfo>();
- list.add(infoA);
- list.add(infoB);
- list.add(infoC);
- list.add(infoD);
- list.add(infoE);
-
- // Sort by size.
- Collections.sort(list, new Sort.ObjectsInfoBySize());
- assertEquals(infoB, list.get(0));
- assertEquals(infoA, list.get(1));
- assertEquals(infoC, list.get(2));
- assertEquals(infoD, list.get(3));
- assertEquals(infoE, list.get(4));
-
- // Sort by class name.
- Collections.sort(list, new Sort.ObjectsInfoByClassName());
- assertEquals(classA, list.get(0).classObj);
- assertEquals(classA, list.get(1).classObj);
- assertEquals(classB, list.get(2).classObj);
- assertEquals(classB, list.get(3).classObj);
- assertEquals(classC, list.get(4).classObj);
-
- // Sort by heap name.
- Collections.sort(list, new Sort.ObjectsInfoByHeapName());
- assertEquals(heapA, list.get(0).heap);
- assertEquals(heapA, list.get(1).heap);
- assertEquals(heapA, list.get(2).heap);
- assertEquals(heapB, list.get(3).heap);
- assertEquals(heapB, list.get(4).heap);
-
- // Sort first by class name, then by size.
- Collections.sort(list, new Sort.WithPriority<Site.ObjectsInfo>(
- new Sort.ObjectsInfoByClassName(),
- new Sort.ObjectsInfoBySize()));
- assertEquals(infoA, list.get(0));
- assertEquals(infoD, list.get(1));
- assertEquals(infoB, list.get(2));
- assertEquals(infoE, list.get(3));
- assertEquals(infoC, list.get(4));
- }
-}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index ebce61c..531c9dd 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -16,14 +16,15 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Value;
import com.android.tools.perflib.heap.ProguardMap;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
-import java.util.Map;
/**
* The TestDump class is used to get an AhatSnapshot for the test-dump
@@ -71,18 +72,27 @@
}
/**
- * Return the value of a field in the DumpedStuff instance in the
+ * Returns the value of a field in the DumpedStuff instance in the
* snapshot for the test-dump program.
*/
- public Object getDumpedThing(String name) {
- ClassObj main = mSnapshot.findClass("Main");
- Instance stuff = null;
- for (Map.Entry<Field, Object> fields : main.getStaticFieldValues().entrySet()) {
- if ("stuff".equals(fields.getKey().getName())) {
- stuff = (Instance) fields.getValue();
+ public Value getDumpedValue(String name) {
+ AhatClassObj main = mSnapshot.findClass("Main");
+ AhatInstance stuff = null;
+ for (FieldValue fields : main.getStaticFieldValues()) {
+ if ("stuff".equals(fields.getName())) {
+ stuff = fields.getValue().asAhatInstance();
}
}
- return InstanceUtils.getField(stuff, name);
+ return stuff.getField(name);
+ }
+
+ /**
+ * Returns the value of a non-primitive field in the DumpedStuff instance in
+ * the snapshot for the test-dump program.
+ */
+ public AhatInstance getDumpedAhatInstance(String name) {
+ Value value = getDumpedValue(name);
+ return value == null ? null : value.asAhatInstance();
}
/**
diff --git a/tools/ahat/test/TestHandler.java b/tools/ahat/test/TestHandler.java
new file mode 100644
index 0000000..859e39a
--- /dev/null
+++ b/tools/ahat/test/TestHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Provide common utilities for basic handler tests.
+ */
+public class TestHandler {
+ private static class NullOutputStream extends OutputStream {
+ public void write(int b) throws IOException {
+ }
+ }
+
+ /**
+ * Test that the given handler doesn't crash on the given query.
+ */
+ public static void testNoCrash(AhatHandler handler, String uri) throws IOException {
+ PrintStream ps = new PrintStream(new NullOutputStream());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("noCrash test"), DocString.uri("style.css"));
+ Query query = new Query(DocString.uri(uri));
+ handler.handle(doc, query);
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index 3291470..6c29f27 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,11 +22,14 @@
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
- "com.android.ahat.InstanceUtilsTest",
+ "com.android.ahat.InstanceTest",
"com.android.ahat.NativeAllocationTest",
+ "com.android.ahat.ObjectHandlerTest",
+ "com.android.ahat.OverviewHandlerTest",
"com.android.ahat.PerformanceTest",
+ "com.android.ahat.RootedHandlerTest",
"com.android.ahat.QueryTest",
- "com.android.ahat.SortTest",
+ "com.android.ahat.SiteHandlerTest",
};
}
JUnitCore.main(args);