Show registered native allocations in ahat.
Bug: 23130675
Change-Id: I1d7f41a47a956b30611429b9bd395ec5f9580209
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index a3ecf86..da5225c 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -77,6 +77,9 @@
* Instance.isRoot and Instance.getRootTypes.
Release History:
+ 0.4 Pending
+ Show registered native allocations for heap dumps that support it.
+
0.3 Dec 15, 2015
Fix page loading performance by showing a limited number of entries by default.
Fix mismatch between overview and "roots" totals.
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
index fc7911b..2adec6f 100644
--- a/tools/ahat/src/AhatSnapshot.java
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -43,22 +43,27 @@
* ahat.
*/
class AhatSnapshot {
- private Snapshot mSnapshot;
- private List<Heap> mHeaps;
+ private final Snapshot mSnapshot;
+ private final List<Heap> mHeaps;
// Map from Instance to the list of Instances it immediately dominates.
- private Map<Instance, List<Instance>> mDominated;
+ private final Map<Instance, List<Instance>> mDominated
+ = new HashMap<Instance, List<Instance>>();
// Collection of objects whose immediate dominator is the SENTINEL_ROOT.
- private List<Instance> mRooted;
+ 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 Map<Instance, Collection<RootType>> mRoots;
+ private final Map<Instance, Collection<RootType>> mRoots
+ = new HashMap<Instance, Collection<RootType>>();
- private Site mRootSite;
- private Map<Heap, Long> mHeapSizes;
+ 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.
@@ -77,10 +82,6 @@
private AhatSnapshot(Snapshot snapshot) {
mSnapshot = snapshot;
mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
- mDominated = new HashMap<Instance, List<Instance>>();
- mRootSite = new Site("ROOT");
- mHeapSizes = new HashMap<Heap, Long>();
- mRooted = new ArrayList<Instance>();
ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
for (Heap heap : mHeaps) {
@@ -118,13 +119,18 @@
}
}
mRootSite.add(stackId, 0, path.iterator(), inst);
+
+ // Update native allocations.
+ InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst);
+ if (alloc != null) {
+ mNativeAllocations.add(alloc);
+ }
}
}
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);
@@ -259,4 +265,9 @@
}
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/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
index 7fa53c7..8b7f9ea 100644
--- a/tools/ahat/src/InstanceUtils.java
+++ b/tools/ahat/src/InstanceUtils.java
@@ -20,6 +20,7 @@
import com.android.tools.perflib.heap.ClassInstance;
import com.android.tools.perflib.heap.ClassObj;
import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Heap;
import com.android.tools.perflib.heap.Type;
import java.awt.image.BufferedImage;
@@ -31,7 +32,7 @@
* Returns true if the given instance is an instance of a class with the
* given name.
*/
- public static boolean isInstanceOfClass(Instance inst, String className) {
+ private static boolean isInstanceOfClass(Instance inst, String className) {
ClassObj cls = (inst == null) ? null : inst.getClassObj();
return (cls != null && className.equals(cls.getClassName()));
}
@@ -118,12 +119,12 @@
return null;
}
- Integer width = getIntField(inst, "mWidth");
+ Integer width = getIntField(inst, "mWidth", null);
if (width == null) {
return null;
}
- Integer height = getIntField(inst, "mHeight");
+ Integer height = getIntField(inst, "mHeight", null);
if (height == null) {
return null;
}
@@ -186,23 +187,29 @@
/**
* Read an int field of an instance.
* The field is assumed to be an int type.
- * Returns null if the field value is not an int or could not be read.
+ * 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) {
+ private static Integer getIntField(Instance inst, String fieldName, Integer def) {
Object value = getField(inst, fieldName);
if (!(value instanceof Integer)) {
- return null;
+ return def;
}
return (Integer)value;
}
/**
- * Read an int field of an instance, returning a default value if the field
- * was not an int or could not be read.
+ * 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 int getIntField(Instance inst, String fieldName, int def) {
- Integer value = getIntField(inst, fieldName);
- return value == null ? def : value;
+ private static Long getLongField(Instance inst, String fieldName, Long def) {
+ Object value = getField(inst, fieldName);
+ if (!(value instanceof Long)) {
+ return def;
+ }
+ return (Long)value;
}
/**
@@ -278,4 +285,73 @@
}
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.getHardReferences()) {
+ 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);
+ }
}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 091820f..d784599 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -78,6 +78,7 @@
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"));
diff --git a/tools/ahat/src/Menu.java b/tools/ahat/src/Menu.java
index 018e019..232b849 100644
--- a/tools/ahat/src/Menu.java
+++ b/tools/ahat/src/Menu.java
@@ -27,6 +27,8 @@
.append(" - ")
.appendLink(DocString.uri("sites"), DocString.text("allocations"))
.append(" - ")
+ .appendLink(DocString.uri("native"), DocString.text("native"))
+ .append(" - ")
.appendLink(DocString.uri("help"), DocString.text("help"));
/**
diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java
new file mode 100644
index 0000000..17407e1
--- /dev/null
+++ b/tools/ahat/src/NativeAllocationsHandler.java
@@ -0,0 +1,95 @@
+/*
+ * 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/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index 720fcb4..0dbad7e 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -48,6 +48,22 @@
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());
}
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
index 3b79166..c5f89c3 100644
--- a/tools/ahat/src/Sort.java
+++ b/tools/ahat/src/Sort.java
@@ -177,5 +177,31 @@
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/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 90cd7af..701d60e 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -19,6 +19,7 @@
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
+import libcore.util.NativeAllocationRegistry;
/**
* Program used to create a heap dump for test purposes.
@@ -47,6 +48,9 @@
for (int i = 0; i < N; i++) {
bigArray[i] = (byte)((i*i) & 0xFF);
}
+
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(0x12345, 42);
+ registry.registerNativeAllocation(anObject, 0xABCDABCD);
}
}
diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java
new file mode 100644
index 0000000..7ad4c1d
--- /dev/null
+++ b/tools/ahat/test/NativeAllocationTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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/Tests.java b/tools/ahat/test/Tests.java
index e8894e2..3291470 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -23,6 +23,7 @@
if (args.length == 0) {
args = new String[]{
"com.android.ahat.InstanceUtilsTest",
+ "com.android.ahat.NativeAllocationTest",
"com.android.ahat.PerformanceTest",
"com.android.ahat.QueryTest",
"com.android.ahat.SortTest",