summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/ahat/Android.mk15
-rw-r--r--tools/ahat/README.txt27
-rw-r--r--tools/ahat/src/AhatSnapshot.java287
-rw-r--r--tools/ahat/src/BitmapHandler.java7
-rw-r--r--tools/ahat/src/Column.java12
-rw-r--r--tools/ahat/src/DocString.java73
-rw-r--r--tools/ahat/src/DominatedList.java33
-rw-r--r--tools/ahat/src/HeapTable.java83
-rw-r--r--tools/ahat/src/HelpHandler.java52
-rw-r--r--tools/ahat/src/HtmlDoc.java36
-rw-r--r--tools/ahat/src/InstanceUtils.java457
-rw-r--r--tools/ahat/src/Main.java45
-rw-r--r--tools/ahat/src/Menu.java6
-rw-r--r--tools/ahat/src/NativeAllocationsHandler.java95
-rw-r--r--tools/ahat/src/ObjectHandler.java227
-rw-r--r--tools/ahat/src/ObjectsHandler.java30
-rw-r--r--tools/ahat/src/OverviewHandler.java46
-rw-r--r--tools/ahat/src/RootedHandler.java1
-rw-r--r--tools/ahat/src/Site.java199
-rw-r--r--tools/ahat/src/SiteHandler.java48
-rw-r--r--tools/ahat/src/SitePrinter.java14
-rw-r--r--tools/ahat/src/StaticHandler.java4
-rw-r--r--tools/ahat/src/Summarizer.java143
-rw-r--r--tools/ahat/src/Value.java107
-rw-r--r--tools/ahat/src/heapdump/AhatArrayInstance.java229
-rw-r--r--tools/ahat/src/heapdump/AhatClassInstance.java224
-rw-r--r--tools/ahat/src/heapdump/AhatClassObj.java115
-rw-r--r--tools/ahat/src/heapdump/AhatField.java42
-rw-r--r--tools/ahat/src/heapdump/AhatHeap.java89
-rw-r--r--tools/ahat/src/heapdump/AhatInstance.java455
-rw-r--r--tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java71
-rw-r--r--tools/ahat/src/heapdump/AhatPlaceHolderInstance.java63
-rw-r--r--tools/ahat/src/heapdump/AhatSnapshot.java293
-rw-r--r--tools/ahat/src/heapdump/Diff.java383
-rw-r--r--tools/ahat/src/heapdump/Diffable.java38
-rw-r--r--tools/ahat/src/heapdump/FieldValue.java83
-rw-r--r--tools/ahat/src/heapdump/PathElement.java37
-rw-r--r--tools/ahat/src/heapdump/Site.java284
-rw-r--r--tools/ahat/src/heapdump/Sort.java (renamed from tools/ahat/src/Sort.java)129
-rw-r--r--tools/ahat/src/heapdump/Value.java133
-rw-r--r--tools/ahat/src/help.html80
-rw-r--r--tools/ahat/src/manifest.txt2
-rw-r--r--tools/ahat/src/style.css8
-rw-r--r--tools/ahat/test-dump/Main.java79
-rw-r--r--tools/ahat/test/DiffTest.java163
-rw-r--r--tools/ahat/test/InstanceTest.java413
-rw-r--r--tools/ahat/test/InstanceUtilsTest.java252
-rw-r--r--tools/ahat/test/NativeAllocationTest.java44
-rw-r--r--tools/ahat/test/ObjectHandlerTest.java74
-rw-r--r--tools/ahat/test/OverviewHandlerTest.java34
-rw-r--r--tools/ahat/test/PerformanceTest.java8
-rw-r--r--tools/ahat/test/QueryTest.java3
-rw-r--r--tools/ahat/test/RootedHandlerTest.java30
-rw-r--r--tools/ahat/test/SiteHandlerTest.java30
-rw-r--r--tools/ahat/test/SnapshotBuilder.java53
-rw-r--r--tools/ahat/test/SortTest.java81
-rw-r--r--tools/ahat/test/TestDump.java121
-rw-r--r--tools/ahat/test/TestHandler.java41
-rw-r--r--tools/ahat/test/Tests.java9
59 files changed, 4225 insertions, 2015 deletions
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 493eafb3c9..f79377d518 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -23,7 +23,6 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAR_MANIFEST := src/manifest.txt
LOCAL_JAVA_RESOURCE_FILES := \
- $(LOCAL_PATH)/src/help.html \
$(LOCAL_PATH)/src/style.css
LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
@@ -79,8 +78,9 @@ include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
# BUILD_HOST_DALVIK_JAVA_LIBRARY above.
AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
+AHAT_TEST_DUMP_BASE_HPROF := $(intermediates.COMMON)/test-dump-base.hprof
-# Run ahat-test-dump.jar to generate test-dump.hprof
+# Run ahat-test-dump.jar to generate test-dump.hprof and test-dump-base.hprof
AHAT_TEST_DUMP_DEPENDENCIES := \
$(ART_HOST_EXECUTABLES) \
$(ART_HOST_SHARED_LIBRARY_DEPENDENCIES) \
@@ -93,12 +93,19 @@ $(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP
$(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
+$(AHAT_TEST_DUMP_BASE_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
+ $(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@ --base
+
.PHONY: ahat-test
ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
+ahat-test: PRIVATE_AHAT_TEST_DUMP_BASE_HPROF := $(AHAT_TEST_DUMP_BASE_HPROF)
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)
+ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF) $(AHAT_TEST_DUMP_BASE_HPROF)
+ java -enableassertions -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.base.hprof=$(PRIVATE_AHAT_TEST_DUMP_BASE_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/README.txt b/tools/ahat/README.txt
index 8dfb4abe5b..08d41f0feb 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -1,22 +1,21 @@
AHAT - Android Heap Analysis Tool
Usage:
- java -jar ahat.jar [-p port] [--proguard-map FILE] FILE
- Launch an http server for viewing the given Android heap-dump FILE.
+ java -jar ahat.jar [OPTIONS] FILE
+ Launch an http server for viewing the given Android heap dump FILE.
- Options:
+ OPTIONS:
-p <port>
Serve pages on the given port. Defaults to 7100.
--proguard-map FILE
Use the proguard map FILE to deobfuscate the heap dump.
+ --baseline FILE
+ Diff the heap dump against the given baseline heap dump FILE.
+ --baseline-proguard-map FILE
+ Use the proguard map FILE to deobfuscate the baseline heap dump.
TODO:
- * Have a way to diff two heap dumps.
-
- * Add more tips to the help page.
- - Recommend how to start looking at a heap dump.
- - Say how to enable allocation sites.
- - Where to submit feedback, questions, and bug reports.
+ * Add a user guide.
* Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
* Let user re-sort sites objects info by clicking column headers.
* Let user re-sort "Objects" list.
@@ -49,9 +48,9 @@ Things to Test:
time.
* That we don't show the 'extra' column in the DominatedList if we are
showing all the instances.
- * That InstanceUtils.asString properly takes into account "offset" and
+ * That Instance.asString properly takes into account "offset" and
"count" fields, if they are present.
- * InstanceUtils.getDexCacheLocation
+ * Instance.getDexCacheLocation
Reported Issues:
* Request to be able to sort tables by size.
@@ -76,7 +75,11 @@ Things to move to perflib:
* Instance.isRoot and Instance.getRootTypes.
Release History:
- 0.9 Pending
+ 1.0 Dec 20, 2016
+ Add support for diffing two heap dumps.
+ Remove native allocations view.
+ Remove outdated help page.
+ Significant refactoring of ahat internals.
0.8 Oct 18, 2016
Show sample path from GC root with field names in place of dominator path.
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
deleted file mode 100644
index ba8243f744..0000000000
--- 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 0f567e3200..836aef67b8 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 @@ class BitmapHandler implements HttpHandler {
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/Column.java b/tools/ahat/src/Column.java
index b7f2829c58..819e586ef9 100644
--- a/tools/ahat/src/Column.java
+++ b/tools/ahat/src/Column.java
@@ -22,14 +22,24 @@ package com.android.ahat;
class Column {
public DocString heading;
public Align align;
+ public boolean visible;
public static enum Align {
LEFT, RIGHT
};
- public Column(DocString heading, Align align) {
+ public Column(DocString heading, Align align, boolean visible) {
this.heading = heading;
this.align = align;
+ this.visible = visible;
+ }
+
+ public Column(String heading, Align align, boolean visible) {
+ this(DocString.text(heading), align, visible);
+ }
+
+ public Column(DocString heading, Align align) {
+ this(heading, align, true);
}
/**
diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java
index 19666dea8c..c6303c8c35 100644
--- a/tools/ahat/src/DocString.java
+++ b/tools/ahat/src/DocString.java
@@ -53,7 +53,6 @@ class DocString {
public static DocString link(URI uri, DocString content) {
DocString doc = new DocString();
return doc.appendLink(uri, content);
-
}
/**
@@ -86,6 +85,78 @@ class DocString {
return this;
}
+ /**
+ * Adorn the given string to indicate it represents something added relative
+ * to a baseline.
+ */
+ public static DocString added(DocString str) {
+ DocString string = new DocString();
+ string.mStringBuilder.append("<span class=\"added\">");
+ string.mStringBuilder.append(str.html());
+ string.mStringBuilder.append("</span>");
+ return string;
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something added relative
+ * to a baseline.
+ */
+ public static DocString added(String str) {
+ return added(text(str));
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something removed relative
+ * to a baseline.
+ */
+ public static DocString removed(DocString str) {
+ DocString string = new DocString();
+ string.mStringBuilder.append("<span class=\"removed\">");
+ string.mStringBuilder.append(str.html());
+ string.mStringBuilder.append("</span>");
+ return string;
+ }
+
+ /**
+ * Adorn the given string to indicate it represents something removed relative
+ * to a baseline.
+ */
+ public static DocString removed(String str) {
+ return removed(text(str));
+ }
+
+ /**
+ * Standard formatted DocString for describing a change in size relative to
+ * a baseline.
+ * @param noCurrent - whether no current object exists.
+ * @param noBaseline - whether no basline object exists.
+ * @param current - the size of the current object.
+ * @param baseline - the size of the baseline object.
+ */
+ public static DocString delta(boolean noCurrent, boolean noBaseline,
+ long current, long baseline) {
+ DocString doc = new DocString();
+ return doc.appendDelta(noCurrent, noBaseline, current, baseline);
+ }
+
+ /**
+ * Standard formatted DocString for describing a change in size relative to
+ * a baseline.
+ */
+ public DocString appendDelta(boolean noCurrent, boolean noBaseline,
+ long current, long baseline) {
+ if (noCurrent) {
+ append(removed(format("%+,14d", 0 - baseline)));
+ } else if (noBaseline) {
+ append(added("new"));
+ } else if (current > baseline) {
+ append(added(format("%+,14d", current - baseline)));
+ } else if (current < baseline) {
+ append(removed(format("%+,14d", current - baseline)));
+ }
+ return this;
+ }
+
public DocString appendLink(URI uri, DocString content) {
mStringBuilder.append("<a href=\"");
mStringBuilder.append(uri.toASCIIString());
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 7a673f556e..f73e3ca027 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -16,8 +16,10 @@
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.Sort;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,39 +41,32 @@ class DominatedList {
* @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 5b840489d2..9abbe4a4ed 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.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.Diffable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -39,21 +41,31 @@ class HeapTable {
*/
public interface TableConfig<T> {
String getHeapsDescription();
- long getSize(T element, Heap heap);
+ long getSize(T element, AhatHeap heap);
List<ValueConfig<T>> getValueConfigs();
}
+ private static DocString sizeString(long size, boolean isPlaceHolder) {
+ DocString string = new DocString();
+ if (isPlaceHolder) {
+ string.append(DocString.removed("del"));
+ } else if (size != 0) {
+ string.appendFormat("%,14d", size);
+ }
+ return string;
+ }
+
/**
* Render the table to the given document.
* @param query - The page query.
* @param id - A unique identifier for the table on the page.
*/
- public static <T> void render(Doc doc, Query query, String id,
+ public static <T extends Diffable<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);
}
}
@@ -61,14 +73,14 @@ class HeapTable {
List<ValueConfig<T>> values = config.getValueConfigs();
// 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));
+ subcols.add(new Column("Δ", Column.Align.RIGHT, snapshot.isDiffed()));
}
- if (showTotal) {
- subcols.add(new Column("Total", Column.Align.RIGHT));
- }
+ boolean showTotal = heaps.size() > 1;
+ subcols.add(new Column("Total", Column.Align.RIGHT, showTotal));
+ subcols.add(new Column("Δ", Column.Align.RIGHT, showTotal && snapshot.isDiffed()));
List<Column> cols = new ArrayList<Column>();
for (ValueConfig value : values) {
cols.add(new Column(value.getDescription()));
@@ -79,16 +91,20 @@ class HeapTable {
SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
ArrayList<DocString> vals = new ArrayList<DocString>();
for (T elem : selector.selected()) {
+ T base = elem.getBaseline();
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ long basetotal = 0;
+ for (AhatHeap heap : heaps) {
long size = config.getSize(elem, heap);
+ long basesize = config.getSize(base, heap.getBaseline());
total += size;
- vals.add(size == 0 ? DocString.text("") : DocString.format("%,14d", size));
- }
- if (showTotal) {
- vals.add(total == 0 ? DocString.text("") : DocString.format("%,14d", total));
+ basetotal += basesize;
+ vals.add(sizeString(size, elem.isPlaceHolder()));
+ vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize));
}
+ vals.add(sizeString(total, elem.isPlaceHolder()));
+ vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal));
for (ValueConfig<T> value : values) {
vals.add(value.render(elem));
@@ -99,27 +115,36 @@ class HeapTable {
// 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>();
+ Map<AhatHeap, Long> basesummary = new HashMap<AhatHeap, Long>();
+ for (AhatHeap heap : heaps) {
summary.put(heap, 0L);
+ basesummary.put(heap, 0L);
}
for (T elem : remaining) {
- for (Heap heap : heaps) {
- summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
+ for (AhatHeap heap : heaps) {
+ long size = config.getSize(elem, heap);
+ summary.put(heap, summary.get(heap) + size);
+
+ long basesize = config.getSize(elem.getBaseline(), heap.getBaseline());
+ basesummary.put(heap, basesummary.get(heap) + basesize);
}
}
vals.clear();
long total = 0;
- for (Heap heap : heaps) {
+ long basetotal = 0;
+ for (AhatHeap heap : heaps) {
long size = summary.get(heap);
+ long basesize = basesummary.get(heap);
total += size;
- vals.add(DocString.format("%,14d", size));
- }
- if (showTotal) {
- vals.add(DocString.format("%,14d", total));
+ basetotal += basesize;
+ vals.add(sizeString(size, false));
+ vals.add(DocString.delta(false, false, size, basesize));
}
+ vals.add(sizeString(total, false));
+ vals.add(DocString.delta(false, false, total, basetotal));
for (ValueConfig<T> value : values) {
vals.add(DocString.text("..."));
@@ -131,11 +156,13 @@ class HeapTable {
}
// Returns true if the given heap has a non-zero size entry.
- public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+ public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap,
TableConfig<T> config, List<T> elements) {
- if (snapshot.getHeapSize(heap) > 0) {
+ AhatHeap baseheap = heap.getBaseline();
+ if (heap.getSize() > 0 || baseheap.getSize() > 0) {
for (T element : elements) {
- if (config.getSize(element, heap) > 0) {
+ if (config.getSize(element, heap) > 0 ||
+ config.getSize(element.getBaseline(), baseheap) > 0) {
return true;
}
}
diff --git a/tools/ahat/src/HelpHandler.java b/tools/ahat/src/HelpHandler.java
deleted file mode 100644
index 8de3c85f5c..0000000000
--- a/tools/ahat/src/HelpHandler.java
+++ /dev/null
@@ -1,52 +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.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-
-/**
- * HelpHandler.
- *
- * HttpHandler to show the help page.
- */
-class HelpHandler implements HttpHandler {
-
- @Override
- public void handle(HttpExchange exchange) throws IOException {
- ClassLoader loader = HelpHandler.class.getClassLoader();
- exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
- exchange.sendResponseHeaders(200, 0);
- PrintStream ps = new PrintStream(exchange.getResponseBody());
- HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
- doc.menu(Menu.getMenu());
-
- InputStream is = loader.getResourceAsStream("help.html");
- if (is == null) {
- ps.println("No help available.");
- } else {
- ByteStreams.copy(is, ps);
- }
-
- doc.close();
- ps.close();
- }
-}
diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/HtmlDoc.java
index 5ccbacb2d6..5a22fc75fe 100644
--- a/tools/ahat/src/HtmlDoc.java
+++ b/tools/ahat/src/HtmlDoc.java
@@ -86,19 +86,27 @@ public class HtmlDoc implements Doc {
mCurrentTableColumns = columns;
ps.println("<table>");
for (int i = 0; i < columns.length - 1; i++) {
- ps.format("<th>%s</th>", columns[i].heading.html());
+ if (columns[i].visible) {
+ ps.format("<th>%s</th>", columns[i].heading.html());
+ }
}
// Align the last header to the left so it's easier to see if the last
// column is very wide.
- ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+ if (columns[columns.length - 1].visible) {
+ ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+ }
}
@Override
public void table(DocString description, List<Column> subcols, List<Column> cols) {
mCurrentTableColumns = new Column[subcols.size() + cols.size()];
int j = 0;
+ int visibleSubCols = 0;
for (Column col : subcols) {
+ if (col.visible) {
+ visibleSubCols++;
+ }
mCurrentTableColumns[j] = col;
j++;
}
@@ -108,21 +116,27 @@ public class HtmlDoc implements Doc {
}
ps.println("<table>");
- ps.format("<tr><th colspan=\"%d\">%s</th>", subcols.size(), description.html());
+ ps.format("<tr><th colspan=\"%d\">%s</th>", visibleSubCols, description.html());
for (int i = 0; i < cols.size() - 1; i++) {
- ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+ if (cols.get(i).visible) {
+ ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+ }
}
if (!cols.isEmpty()) {
// Align the last column header to the left so it can still be seen if
// the last column is very wide.
- ps.format("<th align=\"left\" rowspan=\"2\">%s</th>",
- cols.get(cols.size() - 1).heading.html());
+ Column col = cols.get(cols.size() - 1);
+ if (col.visible) {
+ ps.format("<th align=\"left\" rowspan=\"2\">%s</th>", col.heading.html());
+ }
}
ps.println("</tr>");
ps.print("<tr>");
for (Column subcol : subcols) {
- ps.format("<th>%s</th>", subcol.heading.html());
+ if (subcol.visible) {
+ ps.format("<th>%s</th>", subcol.heading.html());
+ }
}
ps.println("</tr>");
}
@@ -141,11 +155,13 @@ public class HtmlDoc implements Doc {
ps.print("<tr>");
for (int i = 0; i < values.length; i++) {
+ if (mCurrentTableColumns[i].visible) {
ps.print("<td");
- if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
- ps.print(" align=\"right\"");
+ if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
+ ps.print(" align=\"right\"");
+ }
+ ps.format(">%s</td>", values[i].html());
}
- ps.format(">%s</td>", values[i].html());
}
ps.println("</tr>");
}
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
deleted file mode 100644
index a062afdaef..0000000000
--- 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 c79b57863e..b8552fe1ce 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,6 +16,8 @@
package com.android.ahat;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
import com.android.tools.perflib.heap.ProguardMap;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
@@ -29,15 +31,18 @@ import java.util.concurrent.Executors;
public class Main {
public static void help(PrintStream out) {
- out.println("java -jar ahat.jar [-p port] [--proguard-map FILE] FILE");
- out.println(" Launch an http server for viewing "
- + "the given Android heap-dump FILE.");
+ out.println("java -jar ahat.jar [OPTIONS] FILE");
+ out.println(" Launch an http server for viewing the given Android heap dump FILE.");
out.println("");
- out.println("Options:");
+ out.println("OPTIONS:");
out.println(" -p <port>");
out.println(" Serve pages on the given port. Defaults to 7100.");
out.println(" --proguard-map FILE");
out.println(" Use the proguard map FILE to deobfuscate the heap dump.");
+ out.println(" --baseline FILE");
+ out.println(" Diff the heap dump against the given baseline heap dump FILE.");
+ out.println(" --baseline-proguard-map FILE");
+ out.println(" Use the proguard map FILE to deobfuscate the baseline heap dump.");
out.println("");
}
@@ -51,7 +56,9 @@ public class Main {
}
File hprof = null;
+ File hprofbase = null;
ProguardMap map = new ProguardMap();
+ ProguardMap mapbase = new ProguardMap();
for (int i = 0; i < args.length; i++) {
if ("-p".equals(args[i]) && i + 1 < args.length) {
i++;
@@ -64,6 +71,22 @@ public class Main {
System.out.println("Unable to read proguard map: " + ex);
System.out.println("The proguard map will not be used.");
}
+ } else if ("--baseline-proguard-map".equals(args[i]) && i + 1 < args.length) {
+ i++;
+ try {
+ mapbase.readFromFile(new File(args[i]));
+ } catch (IOException|ParseException ex) {
+ System.out.println("Unable to read baselline proguard map: " + ex);
+ System.out.println("The proguard map will not be used.");
+ }
+ } else if ("--baseline".equals(args[i]) && i + 1 < args.length) {
+ i++;
+ if (hprofbase != null) {
+ System.err.println("multiple baseline heap dumps.");
+ help(System.err);
+ return;
+ }
+ hprofbase = new File(args[i]);
} else {
if (hprof != null) {
System.err.println("multiple input files.");
@@ -88,17 +111,25 @@ public class Main {
System.out.println("Processing hprof file...");
AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof, map);
- server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof)));
+
+ if (hprofbase != null) {
+ System.out.println("Processing baseline hprof file...");
+ AhatSnapshot base = AhatSnapshot.fromHprof(hprofbase, mapbase);
+
+ System.out.println("Diffing hprof files...");
+ Diff.snapshots(ahat, base);
+ }
+
+ server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof, hprofbase)));
server.createContext("/rooted", new AhatHttpHandler(new RootedHandler(ahat)));
server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat)));
server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat)));
server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat)));
- server.createContext("/native", new AhatHttpHandler(new NativeAllocationsHandler(ahat)));
server.createContext("/bitmap", new BitmapHandler(ahat));
- server.createContext("/help", new HelpHandler());
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/Menu.java b/tools/ahat/src/Menu.java
index 232b849c28..6d38dc5731 100644
--- a/tools/ahat/src/Menu.java
+++ b/tools/ahat/src/Menu.java
@@ -25,11 +25,7 @@ class Menu {
.append(" - ")
.appendLink(DocString.uri("rooted"), DocString.text("rooted"))
.append(" - ")
- .appendLink(DocString.uri("sites"), DocString.text("allocations"))
- .append(" - ")
- .appendLink(DocString.uri("native"), DocString.text("native"))
- .append(" - ")
- .appendLink(DocString.uri("help"), DocString.text("help"));
+ .appendLink(DocString.uri("sites"), DocString.text("allocations"));
/**
* Returns the menu as a DocString.
diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java
deleted file mode 100644
index 17407e1ae1..0000000000
--- a/tools/ahat/src/NativeAllocationsHandler.java
+++ /dev/null
@@ -1,95 +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 java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-class NativeAllocationsHandler implements AhatHandler {
- private static final String ALLOCATIONS_ID = "allocations";
-
- private AhatSnapshot mSnapshot;
-
- public NativeAllocationsHandler(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
- @Override
- public void handle(Doc doc, Query query) throws IOException {
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
-
- doc.title("Registered Native Allocations");
-
- doc.section("Overview");
- long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
- totalSize += alloc.size;
- }
- doc.descriptions();
- doc.description(DocString.text("Number of Registered Native Allocations"),
- DocString.format("%,14d", allocs.size()));
- doc.description(DocString.text("Total Size of Registered Native Allocations"),
- DocString.format("%,14d", totalSize));
- doc.end();
-
- doc.section("List of Allocations");
- if (allocs.isEmpty()) {
- doc.println(DocString.text("(none)"));
- } else {
- doc.table(
- new Column("Size", Column.Align.RIGHT),
- new Column("Heap"),
- new Column("Native Pointer"),
- new Column("Referent"));
- Comparator<InstanceUtils.NativeAllocation> compare
- = new Sort.WithPriority<InstanceUtils.NativeAllocation>(
- new Sort.NativeAllocationByHeapName(),
- new Sort.NativeAllocationBySize());
- Collections.sort(allocs, compare);
- SubsetSelector<InstanceUtils.NativeAllocation> selector
- = new SubsetSelector(query, ALLOCATIONS_ID, allocs);
- for (InstanceUtils.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));
- }
-
- // Print a summary of the remaining entries if there are any.
- List<InstanceUtils.NativeAllocation> remaining = selector.remaining();
- if (!remaining.isEmpty()) {
- long total = 0;
- for (InstanceUtils.NativeAllocation alloc : remaining) {
- total += alloc.size;
- }
-
- doc.row(
- DocString.format("%,14d", total),
- DocString.text("..."),
- DocString.text("..."),
- DocString.text("..."));
- }
-
- doc.end();
- selector.render(doc);
- }
- }
-}
-
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 78aac178e9..2e0ae6ed2d 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -16,22 +16,23 @@
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.Diff;
+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 java.util.Objects;
-import static com.android.ahat.InstanceUtils.PathElement;
class ObjectHandler implements AhatHandler {
@@ -53,35 +54,43 @@ class ObjectHandler implements AhatHandler {
@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;
}
+ AhatInstance base = inst.getBaseline();
- 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("Size"), DocString.format("%d", inst.getSize()));
- doc.description(
- DocString.text("Retained Size"),
- DocString.format("%d", inst.getTotalRetainedSize()));
+ doc.description(DocString.text("Class"), Summarizer.summarize(cls));
+
+ DocString sizeDescription = DocString.format("%,14d ", inst.getSize());
+ sizeDescription.appendDelta(false, base.isPlaceHolder(),
+ inst.getSize(), base.getSize());
+ doc.description(DocString.text("Size"), sizeDescription);
+
+ DocString rsizeDescription = DocString.format("%,14d ", inst.getTotalRetainedSize());
+ rsizeDescription.appendDelta(false, base.isPlaceHolder(),
+ inst.getTotalRetainedSize(), base.getTotalRetainedSize());
+ doc.description(DocString.text("Retained Size"), rsizeDescription);
+
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 +99,146 @@ class ObjectHandler implements AhatHandler {
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()) {
- doc.row(
- DocString.text(field.getField().getType().toString()),
- DocString.text(field.getField().getName()),
- Value.render(snapshot, field.getValue()));
+ AhatInstance base = inst.getBaseline();
+ List<FieldValue> fields = inst.getInstanceFields();
+ if (!base.isPlaceHolder()) {
+ Diff.fields(fields, base.asClassInstance().getInstanceFields());
}
- doc.end();
+ SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
+ printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
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);
+ AhatInstance base = array.getBaseline();
+ boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
+ doc.table(
+ new Column("Index", Column.Align.RIGHT),
+ new Column("Value"),
+ new Column("Δ", Column.Align.LEFT, diff));
+
+ 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 current : selector.selected()) {
+ DocString delta = new DocString();
+ if (diff) {
+ Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
+ if (!Objects.equals(current, previous)) {
+ delta.append("was ");
+ delta.append(Summarizer.summarize(previous));
+ }
+ }
+ doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
i++;
}
doc.end();
selector.render(doc);
}
- private static void printClassInfo(
- Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) {
+ private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
+ doc.table(
+ new Column("Type"),
+ new Column("Name"),
+ new Column("Value"),
+ new Column("Δ", Column.Align.LEFT, diff));
+
+ for (FieldValue field : fields) {
+ Value current = field.getValue();
+ DocString value;
+ if (field.isPlaceHolder()) {
+ value = DocString.removed("del");
+ } else {
+ value = Summarizer.summarize(current);
+ }
+
+ DocString delta = new DocString();
+ FieldValue basefield = field.getBaseline();
+ if (basefield.isPlaceHolder()) {
+ delta.append(DocString.added("new"));
+ } else {
+ Value previous = Value.getBaseline(basefield.getValue());
+ if (!Objects.equals(current, previous)) {
+ delta.append("was ");
+ delta.append(Summarizer.summarize(previous));
+ }
+ }
+ doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
+ }
+ doc.end();
+ }
+
+ 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()) {
- doc.row(
- DocString.text(field.getKey().getType().toString()),
- DocString.text(field.getKey().getName()),
- Value.render(snapshot, field.getValue()));
+ AhatInstance base = clsobj.getBaseline();
+ List<FieldValue> fields = clsobj.getStaticFieldValues();
+ if (!base.isPlaceHolder()) {
+ Diff.fields(fields, base.asClassObj().getStaticFieldValues());
}
- doc.end();
+ SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+ printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
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,25 +246,25 @@ class ObjectHandler implements AhatHandler {
}
}
- 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);
+ // Add a dummy PathElement as a marker for the root.
+ final PathElement root = new PathElement(null, null);
+ path.add(0, root);
HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
public String getHeapsDescription() {
return "Bytes Retained by Heap (Dominators Only)";
}
- public long getSize(PathElement element, Heap heap) {
- if (element == null) {
- return mSnapshot.getHeapSize(heap);
+ public long getSize(PathElement element, AhatHeap heap) {
+ if (element == root) {
+ return heap.getSize();
}
if (element.isDominator) {
- int index = mSnapshot.getHeapIndex(heap);
- return element.instance.getRetainedSize(index);
+ return element.instance.getRetainedSize(heap);
}
return 0;
}
@@ -233,11 +276,11 @@ class ObjectHandler implements AhatHandler {
}
public DocString render(PathElement element) {
- if (element == null) {
+ if (element == root) {
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 +292,9 @@ class ObjectHandler implements AhatHandler {
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 4cfb0a55cf..3062d23b53 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -16,7 +16,10 @@
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 com.android.ahat.heapdump.Sort;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,17 +36,16 @@ class ObjectsHandler implements AhatHandler {
@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);
}
}
@@ -51,16 +53,22 @@ class ObjectsHandler implements AhatHandler {
Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
doc.title("Objects");
+
doc.table(
new Column("Size", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
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()) {
+ AhatInstance base = inst.getBaseline();
doc.row(
- DocString.format("%,d", inst.getSize()),
+ DocString.format("%,14d", inst.getSize()),
+ DocString.delta(inst.isPlaceHolder(), base.isPlaceHolder(),
+ inst.getSize(), base.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 0dbad7e00c..ea305c4e94 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.Diffable;
import java.io.File;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -28,10 +30,12 @@ class OverviewHandler implements AhatHandler {
private AhatSnapshot mSnapshot;
private File mHprof;
+ private File mBaseHprof;
- public OverviewHandler(AhatSnapshot snapshot, File hprof) {
+ public OverviewHandler(AhatSnapshot snapshot, File hprof, File basehprof) {
mSnapshot = snapshot;
mHprof = hprof;
+ mBaseHprof = basehprof;
}
@Override
@@ -44,42 +48,40 @@ class OverviewHandler implements AhatHandler {
DocString.text("ahat version"),
DocString.format("ahat-%s", OverviewHandler.class.getPackage().getImplementationVersion()));
doc.description(DocString.text("hprof file"), DocString.text(mHprof.toString()));
+ if (mBaseHprof != null) {
+ doc.description(DocString.text("baseline hprof file"), DocString.text(mBaseHprof.toString()));
+ }
doc.end();
doc.section("Heap Sizes");
printHeapSizes(doc, query);
- List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
- if (!allocs.isEmpty()) {
- doc.section("Registered Native Allocations");
- long totalSize = 0;
- for (InstanceUtils.NativeAllocation alloc : allocs) {
- totalSize += alloc.size;
- }
- doc.descriptions();
- doc.description(DocString.text("Number of Registered Native Allocations"),
- DocString.format("%,14d", allocs.size()));
- doc.description(DocString.text("Total Size of Registered Native Allocations"),
- DocString.format("%,14d", totalSize));
- doc.end();
+ doc.big(Menu.getMenu());
+ }
+
+ private static class TableElem implements Diffable<TableElem> {
+ @Override public TableElem getBaseline() {
+ return this;
}
- doc.big(Menu.getMenu());
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
}
private void printHeapSizes(Doc doc, Query query) {
- List<Object> dummy = Collections.singletonList(null);
+ List<TableElem> dummy = Collections.singletonList(new TableElem());
- HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
+ HeapTable.TableConfig<TableElem> table = new HeapTable.TableConfig<TableElem>() {
public String getHeapsDescription() {
return "Bytes Retained by Heap";
}
- public long getSize(Object element, Heap heap) {
- return mSnapshot.getHeapSize(heap);
+ public long getSize(TableElem element, AhatHeap heap) {
+ return heap.getSize();
}
- public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
+ public List<HeapTable.ValueConfig<TableElem>> getValueConfigs() {
return Collections.emptyList();
}
};
diff --git a/tools/ahat/src/RootedHandler.java b/tools/ahat/src/RootedHandler.java
index ec3272fa92..26451a3963 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 dbb84f600e..0000000000
--- 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 839e220ca4..febf1713fb 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -16,7 +16,10 @@
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 com.android.ahat.heapdump.Sort;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
@@ -35,11 +38,13 @@ class SiteHandler implements AhatHandler {
@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");
+ doc.big(Summarizer.summarize(site));
- doc.title("Site %s", site.getName());
doc.section("Allocation Site");
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
@@ -48,15 +53,14 @@ class SiteHandler implements AhatHandler {
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 +70,7 @@ class SiteHandler implements AhatHandler {
}
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);
@@ -79,29 +80,36 @@ class SiteHandler implements AhatHandler {
}
doc.section("Objects Allocated");
+
doc.table(
new Column("Reachable Bytes Allocated", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
new Column("Instances", Column.Align.RIGHT),
+ new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
new Column("Heap"),
new Column("Class"));
+
List<Site.ObjectsInfo> infos = site.getObjectsInfos();
Comparator<Site.ObjectsInfo> compare = new Sort.WithPriority<Site.ObjectsInfo>(
- new Sort.ObjectsInfoByHeapName(),
- new Sort.ObjectsInfoBySize(),
- new Sort.ObjectsInfoByClassName());
+ Sort.OBJECTS_INFO_BY_HEAP_NAME,
+ Sort.OBJECTS_INFO_BY_SIZE,
+ Sort.OBJECTS_INFO_BY_CLASS_NAME);
Collections.sort(infos, compare);
SubsetSelector<Site.ObjectsInfo> selector
= new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
for (Site.ObjectsInfo info : selector.selected()) {
- String className = AhatSnapshot.getClassName(info.classObj);
+ Site.ObjectsInfo baseinfo = info.getBaseline();
+ String className = info.getClassName();
doc.row(
DocString.format("%,14d", info.numBytes),
+ DocString.delta(false, false, info.numBytes, baseinfo.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.delta(false, false, info.numInstances, baseinfo.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 2c06b47a29..21ca2deda4 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 @@ class SitePrinter {
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 @@ class SitePrinter {
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/StaticHandler.java b/tools/ahat/src/StaticHandler.java
index fb7049dcd4..b2805d624d 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/Summarizer.java b/tools/ahat/src/Summarizer.java
new file mode 100644
index 0000000000..016eab44f2
--- /dev/null
+++ b/tools/ahat/src/Summarizer.java
@@ -0,0 +1,143 @@
+/*
+ * 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.AhatInstance;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
+import java.net.URI;
+
+/**
+ * Class for generating a DocString summary of an instance or 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;
+
+ /**
+ * Creates a DocString representing a summary of the given instance.
+ */
+ public static DocString summarize(AhatInstance inst) {
+ DocString formatted = new DocString();
+ if (inst == null) {
+ formatted.append("null");
+ return formatted;
+ }
+
+ // Annotate new objects as new.
+ if (inst.getBaseline().isPlaceHolder()) {
+ formatted.append(DocString.added("new "));
+ }
+
+ // Annotate deleted objects as deleted.
+ if (inst.isPlaceHolder()) {
+ formatted.append(DocString.removed("del "));
+ }
+
+ // Annotate unreachable objects as such.
+ if (!inst.isReachable()) {
+ formatted.append("unreachable ");
+ }
+
+ // Annotate roots as roots.
+ if (inst.isRoot()) {
+ formatted.append("root ");
+ }
+
+ // Annotate classes as classes.
+ DocString linkText = new DocString();
+ if (inst.isClassObj()) {
+ linkText.append("class ");
+ }
+
+ linkText.append(inst.toString());
+
+ if (inst.isPlaceHolder()) {
+ // Don't make links to placeholder objects.
+ formatted.append(linkText);
+ } else {
+ URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
+ formatted.appendLink(objTarget, linkText);
+ }
+
+ // Annotate Strings with their values.
+ String stringValue = inst.asString(kMaxChars);
+ if (stringValue != null) {
+ formatted.appendFormat(" \"%s", stringValue);
+ formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
+ }
+
+ // Annotate Reference with its referent
+ 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(summarize(referent));
+ }
+
+ // Annotate DexCache with its location.
+ String dexCacheLocation = inst.getDexCacheLocation(kMaxChars);
+ if (dexCacheLocation != null) {
+ formatted.appendFormat(" for %s", dexCacheLocation);
+ if (kMaxChars == dexCacheLocation.length()) {
+ formatted.append("...");
+ }
+ }
+
+ // Annotate bitmaps with a thumbnail.
+ AhatInstance bitmap = inst.getAssociatedBitmapInstance();
+ String thumbnail = "";
+ if (bitmap != null) {
+ URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
+ formatted.appendThumbnail(uri, "bitmap image");
+ }
+ return formatted;
+ }
+
+ /**
+ * Creates a DocString summarizing the given value.
+ */
+ 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/Value.java b/tools/ahat/src/Value.java
deleted file mode 100644
index 847692bd10..0000000000
--- a/tools/ahat/src/Value.java
+++ /dev/null
@@ -1,107 +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.Instance;
-import java.net.URI;
-
-/**
- * Class to render an hprof value to a DocString.
- */
-class Value {
-
- // 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.
- */
- private static DocString renderInstance(AhatSnapshot snapshot, Instance inst) {
- DocString formatted = new DocString();
- if (inst == null) {
- formatted.append("(null)");
- return formatted;
- }
-
- // Annotate roots as roots.
- if (snapshot.isRoot(inst)) {
- formatted.append("(root) ");
- }
-
-
- // Annotate classes as classes.
- DocString link = new DocString();
- if (inst instanceof ClassObj) {
- link.append("class ");
- }
-
- link.append(inst.toString());
-
- URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
- formatted.appendLink(objTarget, link);
-
- // Annotate Strings with their values.
- String stringValue = InstanceUtils.asString(inst, kMaxChars);
- if (stringValue != null) {
- formatted.appendFormat(" \"%s", stringValue);
- formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
- }
-
- // Annotate Reference with its referent
- Instance referent = InstanceUtils.getReferent(inst);
- 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));
- }
-
- // Annotate DexCache with its location.
- String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst, kMaxChars);
- if (dexCacheLocation != null) {
- formatted.appendFormat(" for %s", dexCacheLocation);
- if (kMaxChars == dexCacheLocation.length()) {
- formatted.append("...");
- }
- }
-
-
- // Annotate bitmaps with a thumbnail.
- Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
- String thumbnail = "";
- if (bitmap != null) {
- URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
- formatted.appendThumbnail(uri, "bitmap image");
- }
- return formatted;
- }
-
- /**
- * Create 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);
- }
- }
-}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
new file mode 100644
index 0000000000..d88cf94075
--- /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 0000000000..273530af64
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -0,0 +1,224 @@
+/*
+ * 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 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 0000000000..c5ade1d405
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+ }
+
+ @Override AhatInstance newPlaceHolderInstance() {
+ return new AhatPlaceHolderClassObj(this);
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatField.java b/tools/ahat/src/heapdump/AhatField.java
new file mode 100644
index 0000000000..a25ee2869d
--- /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 0000000000..c39adc4b41
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatHeap.java
@@ -0,0 +1,89 @@
+/*
+ * 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 implements Diffable<AhatHeap> {
+ private String mName;
+ private long mSize = 0;
+ private int mIndex;
+ private AhatHeap mBaseline;
+ private boolean mIsPlaceHolder = false;
+
+ AhatHeap(String name, int index) {
+ mName = name;
+ mIndex = index;
+ mBaseline = this;
+ }
+
+ /**
+ * Construct a place holder heap.
+ */
+ private AhatHeap(String name, AhatHeap baseline) {
+ mName = name;
+ mIndex = -1;
+ mBaseline = baseline;
+ baseline.setBaseline(this);
+ mIsPlaceHolder = true;
+ }
+
+ /**
+ * Construct a new place holder heap that has the given baseline heap.
+ */
+ static AhatHeap newPlaceHolderHeap(String name, AhatHeap baseline) {
+ return new AhatHeap(name, baseline);
+ }
+
+ void addToSize(long increment) {
+ mSize += increment;
+ }
+
+ /**
+ * Returns a unique instance for this heap between 0 and the total number of
+ * heaps in this snapshot, or -1 if this is a placeholder heap.
+ */
+ 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;
+ }
+
+ void setBaseline(AhatHeap baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override
+ public AhatHeap getBaseline() {
+ return mBaseline;
+ }
+
+ @Override
+ public boolean isPlaceHolder() {
+ return mIsPlaceHolder;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
new file mode 100644
index 0000000000..e6b9c00384
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -0,0 +1,455 @@
+/*
+ * 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 implements Diffable<AhatInstance> {
+ private long mId;
+ private long mSize;
+ private long mTotalRetainedSize;
+ private long mRetainedSizes[]; // Retained size indexed by heap index
+ private boolean mIsReachable;
+ 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>();
+
+ private AhatInstance mBaseline;
+
+ public AhatInstance(long id) {
+ mId = id;
+ mBaseline = this;
+ }
+
+ /**
+ * 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();
+ mIsReachable = inst.isReachable();
+
+ List<AhatHeap> heaps = snapshot.getHeaps();
+ mRetainedSizes = new long[heaps.size()];
+ 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) {
+ int index = heap.getIndex();
+ return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0;
+ }
+
+ /**
+ * Returns the total number of bytes this instance retains.
+ */
+ public long getTotalRetainedSize() {
+ return mTotalRetainedSize;
+ }
+
+ /**
+ * Returns whether this object is strongly-reachable.
+ */
+ public boolean isReachable() {
+ return mIsReachable;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ public void setBaseline(AhatInstance baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public AhatInstance getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+
+ /**
+ * Returns a new place holder instance corresponding to this instance.
+ */
+ AhatInstance newPlaceHolderInstance() {
+ return new AhatPlaceHolderInstance(this);
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
new file mode 100644
index 0000000000..c6ad87fda5
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+/**
+ * PlaceHolder instance to take the place of a real AhatClassObj for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to newPlaceHolder();
+ */
+public class AhatPlaceHolderClassObj extends AhatClassObj {
+ AhatPlaceHolderClassObj(AhatClassObj baseline) {
+ super(-1);
+ setBaseline(baseline);
+ baseline.setBaseline(this);
+ }
+
+ @Override public long getSize() {
+ return 0;
+ }
+
+ @Override public long getRetainedSize(AhatHeap heap) {
+ return 0;
+ }
+
+ @Override public long getTotalRetainedSize() {
+ return 0;
+ }
+
+ @Override public AhatHeap getHeap() {
+ return getBaseline().getHeap().getBaseline();
+ }
+
+ @Override public String getClassName() {
+ return getBaseline().getClassName();
+ }
+
+ @Override public String toString() {
+ return getBaseline().toString();
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return true;
+ }
+
+ @Override public String getName() {
+ return getBaseline().asClassObj().getName();
+ }
+
+ @Override public AhatClassObj getSuperClassObj() {
+ return getBaseline().asClassObj().getSuperClassObj().getBaseline().asClassObj();
+ }
+
+ @Override public AhatInstance getClassLoader() {
+ return getBaseline().asClassObj().getClassLoader().getBaseline();
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
new file mode 100644
index 0000000000..9412eae9a1
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * Generic PlaceHolder instance to take the place of a real AhatInstance for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to AhatInstance.newPlaceHolder();
+ */
+public class AhatPlaceHolderInstance extends AhatInstance {
+ AhatPlaceHolderInstance(AhatInstance baseline) {
+ super(-1);
+ setBaseline(baseline);
+ baseline.setBaseline(this);
+ }
+
+ @Override public long getSize() {
+ return 0;
+ }
+
+ @Override public long getRetainedSize(AhatHeap heap) {
+ return 0;
+ }
+
+ @Override public long getTotalRetainedSize() {
+ return 0;
+ }
+
+ @Override public AhatHeap getHeap() {
+ return getBaseline().getHeap().getBaseline();
+ }
+
+ @Override public String getClassName() {
+ return getBaseline().getClassName();
+ }
+
+ @Override public String asString(int maxChars) {
+ return getBaseline().asString(maxChars);
+ }
+
+ @Override public String toString() {
+ return getBaseline().toString();
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return true;
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
new file mode 100644
index 0000000000..20b85da763
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -0,0 +1,293 @@
+/*
+ * 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 implements Diffable<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 List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
+
+ private AhatSnapshot mBaseline = this;
+
+ /**
+ * 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();
+ for (Heap heap : heaps) {
+ // Note: mHeaps will not be in index order if snapshot.getHeaps does not
+ // return heaps in index order. That's fine, because we don't rely on
+ // mHeaps being in index order.
+ mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap)));
+ TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance inst) {
+ 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);
+ }
+
+ if (inst.isReachable()) {
+ 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();
+ }
+
+ /**
+ * 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.
+ * Modifications to the returned list are visible to this AhatSnapshot,
+ * which is used by diff to insert place holder heaps.
+ */
+ public List<AhatHeap> getHeaps() {
+ return mHeaps;
+ }
+
+ /**
+ * Returns a collection of instances whose immediate dominator is the
+ * SENTINEL_ROOT.
+ */
+ public List<AhatInstance> getRooted() {
+ return mRooted;
+ }
+
+ /**
+ * Returns the root site for this snapshot.
+ */
+ public Site getRootSite() {
+ return mRootSite;
+ }
+
+ // 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);
+ }
+
+ public void setBaseline(AhatSnapshot baseline) {
+ mBaseline = baseline;
+ }
+
+ /**
+ * Returns true if this snapshot has been diffed against another, different
+ * snapshot.
+ */
+ public boolean isDiffed() {
+ return mBaseline != this;
+ }
+
+ @Override public AhatSnapshot getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
new file mode 100644
index 0000000000..943e6e63f5
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -0,0 +1,383 @@
+/*
+ * 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 java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Diff {
+ /**
+ * Perform a diff between two heap lists.
+ *
+ * Heaps are diffed based on heap name. PlaceHolder heaps will be added to
+ * the given lists as necessary so that every heap in A has a corresponding
+ * heap in B and vice-versa.
+ */
+ private static void heaps(List<AhatHeap> a, List<AhatHeap> b) {
+ int asize = a.size();
+ int bsize = b.size();
+ for (int i = 0; i < bsize; i++) {
+ // Set the B heap's baseline as null to mark that we have not yet
+ // matched it with an A heap.
+ b.get(i).setBaseline(null);
+ }
+
+ for (int i = 0; i < asize; i++) {
+ AhatHeap aheap = a.get(i);
+ aheap.setBaseline(null);
+ for (int j = 0; j < bsize; j++) {
+ AhatHeap bheap = b.get(j);
+ if (bheap.getBaseline() == null && aheap.getName().equals(bheap.getName())) {
+ // We found a match between aheap and bheap.
+ aheap.setBaseline(bheap);
+ bheap.setBaseline(aheap);
+ break;
+ }
+ }
+
+ if (aheap.getBaseline() == null) {
+ // We did not find any match for aheap in snapshot B.
+ // Create a placeholder heap in snapshot B to use as the baseline.
+ b.add(AhatHeap.newPlaceHolderHeap(aheap.getName(), aheap));
+ }
+ }
+
+ // Make placeholder heaps in snapshot A for any unmatched heaps in
+ // snapshot B.
+ for (int i = 0; i < bsize; i++) {
+ AhatHeap bheap = b.get(i);
+ if (bheap.getBaseline() == null) {
+ a.add(AhatHeap.newPlaceHolderHeap(bheap.getName(), bheap));
+ }
+ }
+ }
+
+ /**
+ * Key represents an equivalence class of AhatInstances that are allowed to
+ * be considered for correspondence between two different snapshots.
+ */
+ private static class Key {
+ // Corresponding objects must belong to classes of the same name.
+ private final String mClass;
+
+ // Corresponding objects must belong to heaps of the same name.
+ private final String mHeapName;
+
+ // Corresponding string objects must have the same value.
+ // mStringValue is set to the empty string for non-string objects.
+ private final String mStringValue;
+
+ // Corresponding class objects must have the same class name.
+ // mClassName is set to the empty string for non-class objects.
+ private final String mClassName;
+
+ // Corresponding array objects must have the same length.
+ // mArrayLength is set to 0 for non-array objects.
+ private final int mArrayLength;
+
+
+ private Key(AhatInstance inst) {
+ mClass = inst.getClassName();
+ mHeapName = inst.getHeap().getName();
+ mClassName = inst.isClassObj() ? inst.asClassObj().getName() : "";
+ String string = inst.asString();
+ mStringValue = string == null ? "" : string;
+ AhatArrayInstance array = inst.asArrayInstance();
+ mArrayLength = array == null ? 0 : array.getLength();
+ }
+
+ /**
+ * Return the key for the given instance.
+ */
+ public static Key keyFor(AhatInstance inst) {
+ return new Key(inst);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Key)) {
+ return false;
+ }
+ Key o = (Key)other;
+ return mClass.equals(o.mClass)
+ && mHeapName.equals(o.mHeapName)
+ && mStringValue.equals(o.mStringValue)
+ && mClassName.equals(o.mClassName)
+ && mArrayLength == o.mArrayLength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mClass, mHeapName, mStringValue, mClassName, mArrayLength);
+ }
+ }
+
+ private static class InstanceListPair {
+ public final List<AhatInstance> a;
+ public final List<AhatInstance> b;
+
+ public InstanceListPair() {
+ this.a = new ArrayList<AhatInstance>();
+ this.b = new ArrayList<AhatInstance>();
+ }
+
+ public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
+ /**
+ * Recursively create place holder instances for the given instance and
+ * every instance dominated by that instance.
+ * Returns the place holder instance created for the given instance.
+ * Adds all allocated placeholders to the given placeholders list.
+ */
+ private static AhatInstance createPlaceHolders(AhatInstance inst,
+ List<AhatInstance> placeholders) {
+ // Don't actually use recursion, because we could easily smash the stack.
+ // Instead we iterate.
+ AhatInstance result = inst.newPlaceHolderInstance();
+ placeholders.add(result);
+ Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>();
+ deque.push(inst);
+ while (!deque.isEmpty()) {
+ inst = deque.pop();
+
+ for (AhatInstance child : inst.getDominated()) {
+ placeholders.add(child.newPlaceHolderInstance());
+ deque.push(child);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Recursively diff two dominator trees of instances.
+ * PlaceHolder objects are appended to the lists as needed to ensure every
+ * object has a corresponding baseline in the other list. All PlaceHolder
+ * objects are also appended to the given placeholders list, so their Site
+ * info can be updated later on.
+ */
+ private static void instances(List<AhatInstance> a, List<AhatInstance> b,
+ List<AhatInstance> placeholders) {
+ // Don't actually use recursion, because we could easily smash the stack.
+ // Instead we iterate.
+ Deque<InstanceListPair> deque = new ArrayDeque<InstanceListPair>();
+ deque.push(new InstanceListPair(a, b));
+ while (!deque.isEmpty()) {
+ InstanceListPair p = deque.pop();
+
+ // Group instances of the same equivalence class together.
+ Map<Key, InstanceListPair> byKey = new HashMap<Key, InstanceListPair>();
+ for (AhatInstance inst : p.a) {
+ Key key = Key.keyFor(inst);
+ InstanceListPair pair = byKey.get(key);
+ if (pair == null) {
+ pair = new InstanceListPair();
+ byKey.put(key, pair);
+ }
+ pair.a.add(inst);
+ }
+ for (AhatInstance inst : p.b) {
+ Key key = Key.keyFor(inst);
+ InstanceListPair pair = byKey.get(key);
+ if (pair == null) {
+ pair = new InstanceListPair();
+ byKey.put(key, pair);
+ }
+ pair.b.add(inst);
+ }
+
+ // diff objects from the same key class.
+ for (InstanceListPair pair : byKey.values()) {
+ // Sort by retained size and assume the elements at the top of the lists
+ // correspond to each other in that order. This could probably be
+ // improved if desired, but it gives good enough results for now.
+ Collections.sort(pair.a, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+ Collections.sort(pair.b, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+
+ int common = Math.min(pair.a.size(), pair.b.size());
+ for (int i = 0; i < common; i++) {
+ AhatInstance ainst = pair.a.get(i);
+ AhatInstance binst = pair.b.get(i);
+ ainst.setBaseline(binst);
+ binst.setBaseline(ainst);
+ deque.push(new InstanceListPair(ainst.getDominated(), binst.getDominated()));
+ }
+
+ // Add placeholder objects for anything leftover.
+ for (int i = common; i < pair.a.size(); i++) {
+ p.b.add(createPlaceHolders(pair.a.get(i), placeholders));
+ }
+
+ for (int i = common; i < pair.b.size(); i++) {
+ p.a.add(createPlaceHolders(pair.b.get(i), placeholders));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the baseline for root and all its descendants to baseline.
+ */
+ private static void setSitesBaseline(Site root, Site baseline) {
+ root.setBaseline(baseline);
+ for (Site child : root.getChildren()) {
+ setSitesBaseline(child, baseline);
+ }
+ }
+
+ /**
+ * Recursively diff the two sites, setting them and their descendants as
+ * baselines for each other as appropriate.
+ *
+ * This requires that instances have already been diffed. In particular, we
+ * require all AhatClassObjs in one snapshot have corresponding (possibly
+ * place-holder) AhatClassObjs in the other snapshot.
+ */
+ private static void sites(Site a, Site b) {
+ // Set the sites as baselines of each other.
+ a.setBaseline(b);
+ b.setBaseline(a);
+
+ // Set the site's ObjectsInfos as baselines of each other. This implicitly
+ // adds new empty ObjectsInfo as needed.
+ for (Site.ObjectsInfo ainfo : a.getObjectsInfos()) {
+ AhatClassObj baseClassObj = null;
+ if (ainfo.classObj != null) {
+ baseClassObj = (AhatClassObj) ainfo.classObj.getBaseline();
+ }
+ ainfo.setBaseline(b.getObjectsInfo(ainfo.heap.getBaseline(), baseClassObj));
+ }
+ for (Site.ObjectsInfo binfo : b.getObjectsInfos()) {
+ AhatClassObj baseClassObj = null;
+ if (binfo.classObj != null) {
+ baseClassObj = (AhatClassObj) binfo.classObj.getBaseline();
+ }
+ binfo.setBaseline(a.getObjectsInfo(binfo.heap.getBaseline(), baseClassObj));
+ }
+
+ // Set B children's baselines as null to mark that we have not yet matched
+ // them with A children.
+ for (Site bchild : b.getChildren()) {
+ bchild.setBaseline(null);
+ }
+
+ for (Site achild : a.getChildren()) {
+ achild.setBaseline(null);
+ for (Site bchild : b.getChildren()) {
+ if (achild.getLineNumber() == bchild.getLineNumber()
+ && achild.getMethodName().equals(bchild.getMethodName())
+ && achild.getSignature().equals(bchild.getSignature())
+ && achild.getFilename().equals(bchild.getFilename())) {
+ // We found a match between achild and bchild.
+ sites(achild, bchild);
+ break;
+ }
+ }
+
+ if (achild.getBaseline() == null) {
+ // We did not find any match for achild in site B.
+ // Use B for the baseline of achild and its descendants.
+ setSitesBaseline(achild, b);
+ }
+ }
+
+ for (Site bchild : b.getChildren()) {
+ if (bchild.getBaseline() == null) {
+ setSitesBaseline(bchild, a);
+ }
+ }
+ }
+
+ /**
+ * Perform a diff of the two snapshots, setting each as the baseline for the
+ * other.
+ */
+ public static void snapshots(AhatSnapshot a, AhatSnapshot b) {
+ a.setBaseline(b);
+ b.setBaseline(a);
+
+ // Diff the heaps of each snapshot.
+ heaps(a.getHeaps(), b.getHeaps());
+
+ // Diff the instances of each snapshot.
+ List<AhatInstance> placeholders = new ArrayList<AhatInstance>();
+ instances(a.getRooted(), b.getRooted(), placeholders);
+
+ // Diff the sites of each snapshot.
+ // This requires the instances have already been diffed.
+ sites(a.getRootSite(), b.getRootSite());
+
+ // Add placeholders to their corresponding sites.
+ // This requires the sites have already been diffed.
+ for (AhatInstance placeholder : placeholders) {
+ placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
+ }
+ }
+
+ /**
+ * Diff two lists of field values.
+ * PlaceHolder objects are added to the given lists as needed to ensure
+ * every FieldValue in A ends up with a corresponding FieldValue in B.
+ */
+ public static void fields(List<FieldValue> a, List<FieldValue> b) {
+ // Fields with the same name and type are considered matching fields.
+ // For simplicity, we assume the matching fields are in the same order in
+ // both A and B, though some fields may be added or removed in either
+ // list. If our assumption is wrong, in the worst case the quality of the
+ // field diff is poor.
+
+ for (int i = 0; i < a.size(); i++) {
+ FieldValue afield = a.get(i);
+ afield.setBaseline(null);
+
+ // Find the matching field in B, if any.
+ for (int j = i; j < b.size(); j++) {
+ FieldValue bfield = b.get(j);
+ if (afield.getName().equals(bfield.getName())
+ && afield.getType().equals(bfield.getType())) {
+ // We found the matching field in B.
+ // Assume fields i, ..., j-1 in B have no match in A.
+ for ( ; i < j; i++) {
+ a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+ }
+
+ afield.setBaseline(bfield);
+ bfield.setBaseline(afield);
+ break;
+ }
+ }
+
+ if (afield.getBaseline() == null) {
+ b.add(i, FieldValue.newPlaceHolderFieldValue(afield));
+ }
+ }
+
+ // All remaining fields in B are unmatched by any in A.
+ for (int i = a.size(); i < b.size(); i++) {
+ a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/Diffable.java b/tools/ahat/src/heapdump/Diffable.java
new file mode 100644
index 0000000000..53442c857e
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diffable.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * An interface for objects that have corresponding objects in a baseline heap
+ * dump.
+ */
+public interface Diffable<T> {
+ /**
+ * Return the baseline object that corresponds to this one.
+ */
+ T getBaseline();
+
+ /**
+ * Returns true if this is a placeholder object.
+ * A placeholder object is used to indicate there is some object in the
+ * baseline heap dump that is not in this heap dump. In that case, we create
+ * a dummy place holder object in this heap dump as an indicator of the
+ * object removed from the baseline heap dump.
+ */
+ boolean isPlaceHolder();
+}
+
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
new file mode 100644
index 0000000000..3f65cd3030
--- /dev/null
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -0,0 +1,83 @@
+/*
+ * 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 implements Diffable<FieldValue> {
+ private final String mName;
+ private final String mType;
+ private final Value mValue;
+ private FieldValue mBaseline;
+ private final boolean mIsPlaceHolder;
+
+ public FieldValue(String name, String type, Value value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ mBaseline = this;
+ mIsPlaceHolder = false;
+ }
+
+ /**
+ * Construct a place holder FieldValue
+ */
+ private FieldValue(FieldValue baseline) {
+ mName = baseline.mName;
+ mType = baseline.mType;
+ mValue = Value.getBaseline(baseline.mValue);
+ mBaseline = baseline;
+ mIsPlaceHolder = true;
+ }
+
+ static FieldValue newPlaceHolderFieldValue(FieldValue baseline) {
+ FieldValue field = new FieldValue(baseline);
+ baseline.setBaseline(field);
+ return field;
+ }
+
+ /**
+ * 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;
+ }
+
+ public void setBaseline(FieldValue baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public FieldValue getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return mIsPlaceHolder;
+ }
+}
diff --git a/tools/ahat/src/heapdump/PathElement.java b/tools/ahat/src/heapdump/PathElement.java
new file mode 100644
index 0000000000..196a24628c
--- /dev/null
+++ b/tools/ahat/src/heapdump/PathElement.java
@@ -0,0 +1,37 @@
+/*
+ * 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 implements Diffable<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;
+ }
+
+ @Override public PathElement getBaseline() {
+ return this;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
new file mode 100644
index 0000000000..738eaf0687
--- /dev/null
+++ b/tools/ahat/src/heapdump/Site.java
@@ -0,0 +1,284 @@
+/*
+ * 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 implements Diffable<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;
+
+ private Site mBaseline;
+
+ public static class ObjectsInfo implements Diffable<ObjectsInfo> {
+ public AhatHeap heap;
+ public AhatClassObj classObj; // May be null.
+ public long numInstances;
+ public long numBytes;
+ private ObjectsInfo baseline;
+
+ public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
+ this.heap = heap;
+ this.classObj = classObj;
+ this.numInstances = numInstances;
+ this.numBytes = numBytes;
+ this.baseline = this;
+ }
+
+ /**
+ * Returns the name of the class this ObjectsInfo is associated with.
+ */
+ public String getClassName() {
+ return classObj == null ? "???" : classObj.getName();
+ }
+
+ public void setBaseline(ObjectsInfo baseline) {
+ this.baseline = baseline;
+ }
+
+ @Override public ObjectsInfo getBaseline() {
+ return baseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+ }
+
+ /**
+ * 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>>();
+ mBaseline = this;
+ }
+
+ /**
+ * 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);
+
+ ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
+ if (inst.isReachable()) {
+ 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();
+
+ 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 >= 0 && 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;
+ }
+
+ /**
+ * Returns the ObjectsInfo at this site for the given heap and class
+ * objects. Creates a new empty ObjectsInfo if none existed before.
+ */
+ ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) {
+ Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap);
+ if (classToObjectsInfo == null) {
+ classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
+ mObjectsInfoMap.put(heap, classToObjectsInfo);
+ }
+
+ ObjectsInfo info = classToObjectsInfo.get(classObj);
+ if (info == null) {
+ info = new ObjectsInfo(heap, classObj, 0, 0);
+ mObjectsInfos.add(info);
+ classToObjectsInfo.put(classObj, info);
+ }
+ return info;
+ }
+
+ 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;
+ }
+
+ void setBaseline(Site baseline) {
+ mBaseline = baseline;
+ }
+
+ @Override public Site getBaseline() {
+ return mBaseline;
+ }
+
+ @Override public boolean isPlaceHolder() {
+ return false;
+ }
+
+ /**
+ * Adds a place holder instance to this site and all parent sites.
+ */
+ void addPlaceHolderInstance(AhatInstance placeholder) {
+ for (Site site = this; site != null; site = site.mParent) {
+ site.mObjects.add(placeholder);
+ }
+ }
+}
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/heapdump/Sort.java
index 8a3d9f2053..93d147a49e 100644
--- a/tools/ahat/src/Sort.java
+++ b/tools/ahat/src/heapdump/Sort.java
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
+package com.android.ahat.heapdump;
import java.util.ArrayList;
import java.util.Arrays;
@@ -33,30 +30,20 @@ import java.util.List;
* with equals. They should not be used for element lookup or search. They
* should only be used for showing elements to the user in different orders.
*/
-class Sort {
- /**
- * Compare instances by their instance id.
- * This sorts instances from smaller id to larger id.
- */
- public static class InstanceById implements Comparator<Instance> {
- @Override
- public int compare(Instance a, Instance b) {
- return Long.compare(a.getId(), b.getId());
- }
- }
-
+public class Sort {
/**
* Compare instances by their total retained size.
* Different instances with the same total retained size are considered
* 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 final Comparator<AhatInstance> INSTANCE_BY_TOTAL_RETAINED_SIZE
+ = new Comparator<AhatInstance>() {
@Override
- public int compare(Instance a, Instance b) {
+ public int compare(AhatInstance a, AhatInstance b) {
return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
}
- }
+ };
/**
* Compare instances by their retained size for a given heap index.
@@ -64,20 +51,16 @@ class Sort {
* 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 +90,18 @@ class Sort {
}
}
- 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);
+ comparators.add(INSTANCE_BY_TOTAL_RETAINED_SIZE);
+ return new WithPriority<AhatInstance>(comparators);
}
/**
@@ -127,10 +110,10 @@ class Sort {
* 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,68 +124,70 @@ class Sort {
}
/**
+ * Compare Sites by the total size of objects allocated.
+ * This sorts sites from larger size to smaller size.
+ */
+ public static final Comparator<Site> SITE_BY_TOTAL_SIZE = new 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(SITE_BY_TOTAL_SIZE);
+ 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.
* This sorts object infos from larger retained size to smaller size.
*/
- public static class ObjectsInfoBySize implements Comparator<Site.ObjectsInfo> {
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_SIZE
+ = new Comparator<Site.ObjectsInfo>() {
@Override
public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
return Long.compare(b.numBytes, a.numBytes);
}
- }
+ };
/**
* Compare Site.ObjectsInfo by heap name.
* Different object infos with the same heap name are considered equal for
* the purposes of comparison.
*/
- public static class ObjectsInfoByHeapName implements Comparator<Site.ObjectsInfo> {
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_HEAP_NAME
+ = new Comparator<Site.ObjectsInfo>() {
@Override
public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
return a.heap.getName().compareTo(b.heap.getName());
}
- }
+ };
/**
* Compare Site.ObjectsInfo by class name.
* Different object infos with the same class name are considered equal for
* the purposes of comparison.
*/
- public static class ObjectsInfoByClassName implements Comparator<Site.ObjectsInfo> {
+ public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_CLASS_NAME
+ = new 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.
- * Different allocations with the same heap name are considered equal for
- * the purposes of comparison.
- */
- public static class NativeAllocationByHeapName
- implements Comparator<InstanceUtils.NativeAllocation> {
- @Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
- return a.heap.getName().compareTo(b.heap.getName());
- }
- }
-
- /**
- * Compare InstanceUtils.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> {
- @Override
- public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
- return Long.compare(b.size, a.size);
- }
- }
+ };
}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
new file mode 100644
index 0000000000..6b2d38f7b1
--- /dev/null
+++ b/tools/ahat/src/heapdump/Value.java
@@ -0,0 +1,133 @@
+/*
+ * 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();
+ }
+
+ public static Value getBaseline(Value value) {
+ if (value == null || !value.isAhatInstance()) {
+ return value;
+ }
+ return new Value(value.asAhatInstance().getBaseline());
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Value) {
+ Value value = (Value)other;
+ return mObject.equals(value.mObject);
+ }
+ return false;
+ }
+}
diff --git a/tools/ahat/src/help.html b/tools/ahat/src/help.html
deleted file mode 100644
index ff04ad2840..0000000000
--- a/tools/ahat/src/help.html
+++ /dev/null
@@ -1,80 +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.
--->
-
-<h1>Help</h1>
-<h2>Information shown by ahat:</h2>
-<ul>
- <li><a href="/">The total bytes retained by heap.</a></li>
- <li><a href="/rooted">A list of rooted objects and their retained sizes for each heap.</a></li>
- <li>Information about each allocated object:
- <ul>
- <li>The allocation site (stack trace) of the object (if available).</li>
- <li>The dominator path from a root to the object.</li>
- <li>The class, (shallow) size, retained size, and heap of the object.</li>
- <li>The bitmap image for the object if the object represents a bitmap.</li>
- <li>The instance fields or array elements of the object.</li>
- <li>The super class, class loader, and static fields of class objects.</li>
- <li>Other objects with references to the object.</li>
- <li>Other objects immediately dominated by the object.</li>
- </ul>
- </li>
- <li>A list of objects, optionally filtered by class, allocation site, and/or
- heap.</li>
- <li><a href="site">Information about each allocation site:</a>
- <ul>
- <li>The stack trace for the allocation site.</li>
- <li>The number of bytes allocated at the allocation site.</li>
- <li>Child sites called from the allocation site.</li>
- <li>The size and count of objects allocated at the site, organized by
- heap and object type.</li>
- </ul>
- </li>
-</ul>
-
-<h2>Tips:</h2>
-<h3>Heaps</h3>
-<p>
-Android heap dumps contain information for multiple heaps. The <b>app</b> heap
-is the memory used by your application. The <b>zygote</b> and <b>image</b>
-heaps are used by the system. You should ignore everything in the zygote and
-image heap and look only at the app heap. This is because changes in your
-application will not effect the zygote or image heaps, and because the zygote
-and image heaps are shared, they don't contribute significantly to your
-applications PSS.
-</p>
-
-<h3>Bitmaps</h3>
-<p>
-Bitmaps store their data using byte[] arrays. Whenever you see a large
-byte[], check if it is a bitmap by looking to see if there is a single
-android.graphics.Bitmap object referring to it. The byte[] will be marked as a
-root, but it is really being retained by the android.graphics.Bitmap object.
-</p>
-
-<h3>DexCaches</h3>
-<p>
-For each DexFile you load, there will be a corresponding DexCache whose size
-is proportional to the number of strings, fields, methods, and classes in your
-dex file. The DexCache entries may or may not be visible depending on the
-version of the Android platform the heap dump is from.
-</p>
-
-<h3>FinalizerReferences</h3>
-<p>
-A FinalizerReference is allocated for every object on the heap that has a
-non-trivial finalizer. These are stored in a linked list reachable from the
-FinalizerReference class object.
-</p>
diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/src/manifest.txt
index 1993910513..87a82b9f99 100644
--- a/tools/ahat/src/manifest.txt
+++ b/tools/ahat/src/manifest.txt
@@ -1,4 +1,4 @@
Name: ahat/
Implementation-Title: ahat
-Implementation-Version: 0.8
+Implementation-Version: 1.0
Main-Class: com.android.ahat.Main
diff --git a/tools/ahat/src/style.css b/tools/ahat/src/style.css
index ca074a526c..47fae1d551 100644
--- a/tools/ahat/src/style.css
+++ b/tools/ahat/src/style.css
@@ -18,6 +18,14 @@ div.menu {
background-color: #eeffff;
}
+span.added {
+ color: #770000;
+}
+
+span.removed {
+ color: #007700;
+}
+
/*
* Most of the columns show numbers of bytes. Numbers should be right aligned.
*/
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 587d9defef..7a05b1cb89 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -18,8 +18,9 @@ import dalvik.system.VMDebug;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
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.
@@ -39,13 +40,32 @@ public class Main {
}
}
+ public static class AddedObject {
+ }
+
+ public static class RemovedObject {
+ }
+
+ public static class UnchangedObject {
+ }
+
+ public static class ModifiedObject {
+ public int value;
+ public String modifiedRefField;
+ public String unmodifiedRefField;
+ }
+
+ public static class StackSmasher {
+ public StackSmasher child;
+ }
+
// We will take a heap dump that includes a single instance of this
// DumpedStuff class. Objects stored as fields in this class can be easily
// found in the hprof dump by searching for the instance of the DumpedStuff
// 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,23 +73,53 @@ public class Main {
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 SoftReference aSoftReference = new SoftReference(new Object());
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;
+ public AddedObject addedObject;
+ public UnchangedObject unchangedObject = new UnchangedObject();
+ public RemovedObject removedObject;
+ public ModifiedObject modifiedObject;
+ public StackSmasher stackSmasher;
+ public StackSmasher stackSmasherAdded;
+ public static String modifiedStaticField;
+ public int[] modifiedArray;
- DumpedStuff() {
- int N = 1000000;
+ DumpedStuff(boolean baseline) {
+ int N = baseline ? 400000 : 1000000;
bigArray = new byte[N];
for (int i = 0; i < N; i++) {
bigArray[i] = (byte)((i*i) & 0xFF);
}
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
- Main.class.getClassLoader(), 0x12345, 42);
- registry.registerNativeAllocation(anObject, 0xABCDABCD);
+ addedObject = baseline ? null : new AddedObject();
+ removedObject = baseline ? new RemovedObject() : null;
+ modifiedObject = new ModifiedObject();
+ modifiedObject.value = baseline ? 5 : 8;
+ modifiedObject.modifiedRefField = baseline ? "A1" : "A2";
+ modifiedObject.unmodifiedRefField = "B";
+ modifiedStaticField = baseline ? "C1" : "C2";
+ modifiedArray = baseline ? new int[]{0,1,2,3} : new int[]{3,1,2,0};
+
+ // Deep matching dominator trees shouldn't smash the stack when we try
+ // to diff them. Make some deep dominator trees to help test it.
+ for (int i = 0; i < 10000; i++) {
+ StackSmasher smasher = new StackSmasher();
+ smasher.child = stackSmasher;
+ stackSmasher = smasher;
+
+ if (!baseline) {
+ smasher = new StackSmasher();
+ smasher.child = stackSmasherAdded;
+ stackSmasherAdded = smasher;
+ }
+ }
gcPathArray[2].right.left = gcPathArray[2].left.right;
}
@@ -82,8 +132,21 @@ public class Main {
}
String file = args[0];
+ // If a --base argument is provided, it means we should generate a
+ // baseline hprof file suitable for using in testing diff.
+ boolean baseline = args.length > 1 && args[1].equals("--base");
+
+ // Enable allocation tracking so we get stack traces in the heap dump.
+ DdmVmInternal.enableRecentAllocations(true);
+
// Allocate the instance of DumpedStuff.
- stuff = new DumpedStuff();
+ stuff = new DumpedStuff(baseline);
+
+ // 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);
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
new file mode 100644
index 0000000000..52b6b7b3ae
--- /dev/null
+++ b/tools/ahat/test/DiffTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+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.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class DiffTest {
+ @Test
+ public void diffMatchedHeap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatHeap a = dump.getAhatSnapshot().getHeap("app");
+ assertNotNull(a);
+ AhatHeap b = dump.getBaselineAhatSnapshot().getHeap("app");
+ assertNotNull(b);
+ assertEquals(a.getBaseline(), b);
+ assertEquals(b.getBaseline(), a);
+ }
+
+ @Test
+ public void diffUnchanged() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance a = dump.getDumpedAhatInstance("unchangedObject");
+ assertNotNull(a);
+
+ AhatInstance b = dump.getBaselineDumpedAhatInstance("unchangedObject");
+ assertNotNull(b);
+ assertEquals(a, b.getBaseline());
+ assertEquals(b, a.getBaseline());
+ assertEquals(a.getSite(), b.getSite().getBaseline());
+ assertEquals(b.getSite(), a.getSite().getBaseline());
+ }
+
+ @Test
+ public void diffAdded() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance a = dump.getDumpedAhatInstance("addedObject");
+ assertNotNull(a);
+ assertNull(dump.getBaselineDumpedAhatInstance("addedObject"));
+ assertTrue(a.getBaseline().isPlaceHolder());
+ }
+
+ @Test
+ public void diffRemoved() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ assertNull(dump.getDumpedAhatInstance("removedObject"));
+ AhatInstance b = dump.getBaselineDumpedAhatInstance("removedObject");
+ assertNotNull(b);
+ assertTrue(b.getBaseline().isPlaceHolder());
+ }
+
+ @Test
+ public void nullClassObj() throws IOException {
+ // Set up a heap dump that has a null classObj.
+ // The heap dump is derived from the InstanceTest.asStringEmbedded test.
+ 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);
+
+ // Diffing should not crash.
+ Diff.snapshots(snapshot, snapshot);
+ }
+
+ @Test
+ public void diffFields() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n6", "t6", null));
+ b.add(new FieldValue("n7", "t7", null));
+
+ Diff.fields(a, b);
+ assertEquals(8, a.size());
+ assertEquals(8, b.size());
+ for (int i = 0; i < 8; i++) {
+ assertEquals(a.get(i), b.get(i).getBaseline());
+ assertEquals(b.get(i), a.get(i).getBaseline());
+ }
+ assertTrue(a.get(1).isPlaceHolder());
+ assertTrue(a.get(7).isPlaceHolder());
+ assertTrue(b.get(4).isPlaceHolder());
+ }
+}
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
new file mode 100644
index 0000000000..3a50150c0e
--- /dev/null
+++ b/tools/ahat/test/InstanceTest.java
@@ -0,0 +1,413 @@
+/*
+ * 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 unreachableReferent() throws IOException {
+ // The test dump program should never be under enough GC pressure for the
+ // soft reference to be cleared. Ensure that ahat will show the soft
+ // reference as having a non-null referent.
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
+ assertNotNull(ref.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 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 fe2706d7d4..0000000000
--- 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
deleted file mode 100644
index 7ad4c1d431..0000000000
--- a/tools/ahat/test/NativeAllocationTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.tools.perflib.heap.Instance;
-import java.io.IOException;
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class NativeAllocationTest {
-
- @Test
- public void nativeAllocation() throws IOException {
- TestDump dump = TestDump.getTestDump();
-
- AhatSnapshot snapshot = dump.getAhatSnapshot();
- Instance referent = (Instance)dump.getDumpedThing("anObject");
- for (InstanceUtils.NativeAllocation alloc : snapshot.getNativeAllocations()) {
- if (alloc.referent == referent) {
- assertEquals(42 , alloc.size);
- assertEquals(referent.getHeap(), alloc.heap);
- assertEquals(0xABCDABCD , alloc.pointer);
- return;
- }
- }
- fail("No native allocation found with anObject as the referent");
- }
-}
-
diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java
new file mode 100644
index 0000000000..cd0ba23013
--- /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 0000000000..c2f773b64b
--- /dev/null
+++ b/tools/ahat/test/OverviewHandlerTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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"),
+ new File("my.base.hprof.file"));
+ TestHandler.testNoCrash(handler, "http://localhost:7100");
+ }
+}
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
index 6e46800603..e13974bb6f 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 @@ public class PerformanceTest {
// 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 40e3322472..5bcf8eafc3 100644
--- a/tools/ahat/test/QueryTest.java
+++ b/tools/ahat/test/QueryTest.java
@@ -18,9 +18,10 @@ package com.android.ahat;
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 0000000000..f325b8e9a7
--- /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 0000000000..37596be8bb
--- /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 0000000000..0eea6357fd
--- /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 02ff7db068..0000000000
--- 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 ebce61c2e8..ceb7346bc4 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -16,14 +16,16 @@
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.Diff;
+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
@@ -37,30 +39,46 @@ public class TestDump {
// is visible to other test cases.
private static TestDump mCachedTestDump = null;
+ // If the test dump fails to load the first time, it will likely fail every
+ // other test we try. Rather than having to wait a potentially very long
+ // time for test dump loading to fail over and over again, record when it
+ // fails and don't try to load it again.
+ private static boolean mTestDumpFailed = false;
+
private AhatSnapshot mSnapshot = null;
+ private AhatSnapshot mBaseline = null;
/**
- * Load the test-dump.hprof file.
- * The location of the file is read from the system property
- * "ahat.test.dump.hprof", which is expected to be set on the command line.
- * For example:
- * java -Dahat.test.dump.hprof=test-dump.hprof -jar ahat-tests.jar
+ * Load the test-dump.hprof and test-dump-base.hprof files.
+ * The location of the files are read from the system properties
+ * "ahat.test.dump.hprof" and "ahat.test.dump.base.hprof", which is expected
+ * to be set on the command line.
+ * The location of the proguard map for both hprof files is read from the
+ * system property "ahat.test.dump.map". For example:
+ * java -Dahat.test.dump.hprof=test-dump.hprof \
+ * -Dahat.test.dump.base.hprof=test-dump-base.hprof \
+ * -Dahat.test.dump.map=proguard.map \
+ * -jar ahat-tests.jar
*
- * An IOException is thrown if there is a failure reading the hprof file or
+ * An IOException is thrown if there is a failure reading the hprof files or
* the proguard map.
*/
private TestDump() throws IOException {
- String hprof = System.getProperty("ahat.test.dump.hprof");
-
- String mapfile = System.getProperty("ahat.test.dump.map");
- ProguardMap map = new ProguardMap();
- try {
- map.readFromFile(new File(mapfile));
- } catch (ParseException e) {
- throw new IOException("Unable to load proguard map", e);
- }
+ // TODO: Make use of the baseline hprof for tests.
+ String hprof = System.getProperty("ahat.test.dump.hprof");
+ String hprofBase = System.getProperty("ahat.test.dump.base.hprof");
+
+ String mapfile = System.getProperty("ahat.test.dump.map");
+ ProguardMap map = new ProguardMap();
+ try {
+ map.readFromFile(new File(mapfile));
+ } catch (ParseException e) {
+ throw new IOException("Unable to load proguard map", e);
+ }
- mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+ mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+ mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map);
+ Diff.snapshots(mSnapshot, mBaseline);
}
/**
@@ -71,18 +89,59 @@ public class TestDump {
}
/**
- * Return the value of a field in the DumpedStuff instance in the
+ * Get the baseline AhatSnapshot for the test dump program.
+ */
+ public AhatSnapshot getBaselineAhatSnapshot() {
+ return mBaseline;
+ }
+
+ /**
+ * 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) {
+ return getDumpedValue(name, mSnapshot);
+ }
+
+ /**
+ * Returns the value of a field in the DumpedStuff instance in the
+ * baseline snapshot for the test-dump program.
+ */
+ public Value getBaselineDumpedValue(String name) {
+ return getDumpedValue(name, mBaseline);
+ }
+
+ /**
+ * Returns the value of a field in the DumpedStuff instance in the
+ * given snapshot for the test-dump program.
+ */
+ private Value getDumpedValue(String name, AhatSnapshot snapshot) {
+ AhatClassObj main = snapshot.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();
+ }
+
+ /**
+ * Returns the value of a non-primitive field in the DumpedStuff instance in
+ * the baseline snapshot for the test-dump program.
+ */
+ public AhatInstance getBaselineDumpedAhatInstance(String name) {
+ Value value = getBaselineDumpedValue(name);
+ return value == null ? null : value.asAhatInstance();
}
/**
@@ -93,8 +152,14 @@ public class TestDump {
* when possible.
*/
public static synchronized TestDump getTestDump() throws IOException {
+ if (mTestDumpFailed) {
+ throw new RuntimeException("Test dump failed before, assuming it will again");
+ }
+
if (mCachedTestDump == null) {
+ mTestDumpFailed = true;
mCachedTestDump = new TestDump();
+ mTestDumpFailed = false;
}
return mCachedTestDump;
}
diff --git a/tools/ahat/test/TestHandler.java b/tools/ahat/test/TestHandler.java
new file mode 100644
index 0000000000..859e39a688
--- /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 3291470df9..2fd3286172 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,11 +22,14 @@ public class Tests {
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
- "com.android.ahat.InstanceUtilsTest",
- "com.android.ahat.NativeAllocationTest",
+ "com.android.ahat.DiffTest",
+ "com.android.ahat.InstanceTest",
+ "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);