Annotate root objects and show their types.
The summaries for root objects are now prefixed with "(root)" to show
they are roots, and the object's root types are listed in the object
view.
Bug: 23784153
Change-Id: Ib52ccb59cf0e3f28ac754c6caa564ebef34866ea
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index 362ae25..adc4d03 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -19,7 +19,6 @@
* Show site context and heap and class filter in "Objects" view?
* Have a menu at the top of an object view with links to the sections?
* Include ahat version and hprof file in the menu at the top of the page?
- * Show root types.
* Heaped Table
- Make sortable by clicking on headers.
* For HeapTable with single heap shown, the heap name isn't centered?
@@ -77,6 +76,7 @@
* Extracting bitmap data from bitmap instances.
* Adding up allocations by stack frame.
* Computing, for each instance, the other instances it dominates.
+ * Instance.isRoot and Instance.getRootTypes.
Release History:
0.2 Oct 20, 2015
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
index 0bf064e..fc7911b 100644
--- a/tools/ahat/src/AhatSnapshot.java
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -19,6 +19,8 @@
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.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;
@@ -29,8 +31,10 @@
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;
@@ -48,6 +52,11 @@
// Collection of objects whose immediate dominator is the SENTINEL_ROOT.
private List<Instance> mRooted;
+ // 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 Map<Instance, Collection<RootType>> mRoots;
+
private Site mRootSite;
private Map<Heap, Long> mHeapSizes;
@@ -113,6 +122,18 @@
}
mHeapSizes.put(heap, total);
}
+
+ // Record the roots and their types.
+ mRoots = new HashMap<Instance, Collection<RootType>>();
+ 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.
@@ -140,6 +161,21 @@
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;
}
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 34a5665..7a673f5 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -71,7 +71,7 @@
}
public DocString render(Instance element) {
- return Value.render(element);
+ return Value.render(mSnapshot, element);
}
};
return Collections.singletonList(value);
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 1305070..06023da 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -23,9 +23,11 @@
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.RootType;
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;
@@ -57,7 +59,7 @@
}
doc.title("Object %08x", inst.getUniqueId());
- doc.big(Value.render(inst));
+ doc.big(Value.render(mSnapshot, inst));
printAllocationSite(doc, query, inst);
printDominatorPath(doc, query, inst);
@@ -65,27 +67,41 @@
doc.section("Object Info");
ClassObj cls = inst.getClassObj();
doc.descriptions();
- doc.description(DocString.text("Class"), Value.render(cls));
+ 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("Heap"), DocString.text(inst.getHeap().getName()));
+
+ Collection<RootType> rootTypes = mSnapshot.getRootTypes(inst);
+ if (rootTypes != null) {
+ DocString types = new DocString();
+ String comma = "";
+ for (RootType type : rootTypes) {
+ types.append(comma);
+ types.append(type.getName());
+ comma = ", ";
+ }
+ doc.description(DocString.text("Root Types"), types);
+ }
+
doc.end();
printBitmap(doc, inst);
if (inst instanceof ClassInstance) {
- printClassInstanceFields(doc, query, (ClassInstance)inst);
+ printClassInstanceFields(doc, query, mSnapshot, (ClassInstance)inst);
} else if (inst instanceof ArrayInstance) {
- printArrayElements(doc, query, (ArrayInstance)inst);
+ printArrayElements(doc, query, mSnapshot, (ArrayInstance)inst);
} else if (inst instanceof ClassObj) {
- printClassInfo(doc, query, (ClassObj)inst);
+ printClassInfo(doc, query, mSnapshot, (ClassObj)inst);
}
- printReferences(doc, query, inst);
+ printReferences(doc, query, mSnapshot, inst);
printDominatedObjects(doc, query, inst);
}
- private static void printClassInstanceFields(Doc doc, Query query, ClassInstance inst) {
+ private static void printClassInstanceFields(
+ Doc doc, Query query, AhatSnapshot snapshot, ClassInstance inst) {
doc.section("Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
SubsetSelector<ClassInstance.FieldValue> selector
@@ -94,31 +110,35 @@
doc.row(
DocString.text(field.getField().getType().toString()),
DocString.text(field.getField().getName()),
- Value.render(field.getValue()));
+ Value.render(snapshot, field.getValue()));
}
doc.end();
selector.render(doc);
}
- private static void printArrayElements(Doc doc, Query query, ArrayInstance array) {
+ private static void printArrayElements(
+ Doc doc, Query query, AhatSnapshot snapshot, ArrayInstance 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);
int i = 0;
for (Object elem : selector.selected()) {
- doc.row(DocString.format("%d", i), Value.render(elem));
+ doc.row(DocString.format("%d", i), Value.render(snapshot, elem));
i++;
}
doc.end();
selector.render(doc);
}
- private static void printClassInfo(Doc doc, Query query, ClassObj clsobj) {
+ private static void printClassInfo(
+ Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) {
doc.section("Class Info");
doc.descriptions();
- doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj()));
- doc.description(DocString.text("Class Loader"), Value.render(clsobj.getClassLoader()));
+ doc.description(DocString.text("Super Class"),
+ Value.render(snapshot, clsobj.getSuperClassObj()));
+ doc.description(DocString.text("Class Loader"),
+ Value.render(snapshot, clsobj.getClassLoader()));
doc.end();
doc.section("Static Fields");
@@ -131,13 +151,14 @@
doc.row(
DocString.text(field.getKey().getType().toString()),
DocString.text(field.getKey().getName()),
- Value.render(field.getValue()));
+ Value.render(snapshot, field.getValue()));
}
doc.end();
selector.render(doc);
}
- private static void printReferences(Doc doc, Query query, Instance inst) {
+ private static void printReferences(
+ Doc doc, Query query, AhatSnapshot snapshot, Instance inst) {
doc.section("Objects with References to this Object");
if (inst.getHardReferences().isEmpty()) {
doc.println(DocString.text("(none)"));
@@ -146,7 +167,7 @@
List<Instance> references = inst.getHardReferences();
SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
for (Instance ref : selector.selected()) {
- doc.row(Value.render(ref));
+ doc.row(Value.render(snapshot, ref));
}
doc.end();
selector.render(doc);
@@ -158,7 +179,7 @@
List<Instance> references = inst.getSoftReferences();
SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
for (Instance ref : selector.selected()) {
- doc.row(Value.render(ref));
+ doc.row(Value.render(snapshot, ref));
}
doc.end();
selector.render(doc);
@@ -217,7 +238,7 @@
if (element == null) {
return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
} else {
- return DocString.text("→ ").append(Value.render(element));
+ return DocString.text("→ ").append(Value.render(mSnapshot, element));
}
}
};
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 8ad3f48..4cfb0a5 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -60,7 +60,7 @@
doc.row(
DocString.format("%,d", inst.getSize()),
DocString.text(inst.getHeap().getName()),
- Value.render(inst));
+ Value.render(mSnapshot, inst));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 0425a5a..839e220 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -101,7 +101,7 @@
site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
DocString.format("%,14d", info.numInstances)),
DocString.text(info.heap.getName()),
- Value.render(info.classObj));
+ Value.render(mSnapshot, info.classObj));
}
doc.end();
selector.render(doc);
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Value.java
index 7c969b3..847692b 100644
--- a/tools/ahat/src/Value.java
+++ b/tools/ahat/src/Value.java
@@ -32,21 +32,29 @@
/**
* Create a DocString representing a summary of the given instance.
*/
- private static DocString renderInstance(Instance inst) {
- DocString link = new DocString();
+ private static DocString renderInstance(AhatSnapshot snapshot, Instance inst) {
+ DocString formatted = new DocString();
if (inst == null) {
- link.append("(null)");
- return link;
+ 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());
- DocString formatted = DocString.link(objTarget, link);
+ formatted.appendLink(objTarget, link);
// Annotate Strings with their values.
String stringValue = InstanceUtils.asString(inst, kMaxChars);
@@ -63,7 +71,7 @@
// 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(referent));
+ formatted.append(renderInstance(snapshot, referent));
}
// Annotate DexCache with its location.
@@ -89,9 +97,9 @@
/**
* Create a DocString summarizing the given value.
*/
- public static DocString render(Object val) {
+ public static DocString render(AhatSnapshot snapshot, Object val) {
if (val instanceof Instance) {
- return renderInstance((Instance)val);
+ return renderInstance(snapshot, (Instance)val);
} else {
return DocString.format("%s", val);
}