From 1a5baaabcdd79997508c91f1d8b1cf8547c9d1cf Mon Sep 17 00:00:00 2001 From: Richard Uhler Date: Mon, 21 Dec 2015 12:47:26 -0800 Subject: [PATCH] Show registered native allocations in ahat. Bug: 23130675 Change-Id: I1d7f41a47a956b30611429b9bd395ec5f9580209 --- tools/ahat/README.txt | 3 + tools/ahat/src/AhatSnapshot.java | 35 ++++++---- tools/ahat/src/InstanceUtils.java | 98 ++++++++++++++++++++++++---- tools/ahat/src/Main.java | 1 + tools/ahat/src/Menu.java | 2 + tools/ahat/src/NativeAllocationsHandler.java | 95 +++++++++++++++++++++++++++ tools/ahat/src/OverviewHandler.java | 16 +++++ tools/ahat/src/Sort.java | 26 ++++++++ tools/ahat/test-dump/Main.java | 4 ++ tools/ahat/test/NativeAllocationTest.java | 44 +++++++++++++ tools/ahat/test/Tests.java | 1 + 11 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 tools/ahat/src/NativeAllocationsHandler.java create mode 100644 tools/ahat/test/NativeAllocationTest.java diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index a3ecf86ab..da5225c06 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -77,6 +77,9 @@ Things to move to perflib: * 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 fc7911b71..2adec6f17 100644 --- a/tools/ahat/src/AhatSnapshot.java +++ b/tools/ahat/src/AhatSnapshot.java @@ -43,22 +43,27 @@ import java.util.Map; * ahat. */ class AhatSnapshot { - private Snapshot mSnapshot; - private List mHeaps; + private final Snapshot mSnapshot; + private final List mHeaps; // Map from Instance to the list of Instances it immediately dominates. - private Map> mDominated; + private final Map> mDominated + = new HashMap>(); // Collection of objects whose immediate dominator is the SENTINEL_ROOT. - private List mRooted; + private final List mRooted = new ArrayList(); // 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> mRoots; + private final Map> mRoots + = new HashMap>(); - private Site mRootSite; - private Map mHeapSizes; + private final Site mRootSite = new Site("ROOT"); + private final Map mHeapSizes = new HashMap(); + + private final List mNativeAllocations + = new ArrayList(); /** * Create an AhatSnapshot from an hprof file. @@ -77,10 +82,6 @@ class AhatSnapshot { private AhatSnapshot(Snapshot snapshot) { mSnapshot = snapshot; mHeaps = new ArrayList(mSnapshot.getHeaps()); - mDominated = new HashMap>(); - mRootSite = new Site("ROOT"); - mHeapSizes = new HashMap(); - mRooted = new ArrayList(); ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class"); for (Heap heap : mHeaps) { @@ -118,13 +119,18 @@ class AhatSnapshot { } } 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>(); for (RootObj root : snapshot.getGCRoots()) { Instance inst = root.getReferredInstance(); Collection types = mRoots.get(inst); @@ -259,4 +265,9 @@ class AhatSnapshot { } return site; } + + // Return a list of known native allocations in the snapshot. + public List getNativeAllocations() { + return mNativeAllocations; + } } diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java index 7fa53c78b..8b7f9ea41 100644 --- a/tools/ahat/src/InstanceUtils.java +++ b/tools/ahat/src/InstanceUtils.java @@ -20,6 +20,7 @@ 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.Instance; +import com.android.tools.perflib.heap.Heap; import com.android.tools.perflib.heap.Type; import java.awt.image.BufferedImage; @@ -31,7 +32,7 @@ class InstanceUtils { * 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 @@ class InstanceUtils { 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 @@ class InstanceUtils { /** * 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 def 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 def 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 @@ class InstanceUtils { } 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 091820f7f..d7845996b 100644 --- a/tools/ahat/src/Main.java +++ b/tools/ahat/src/Main.java @@ -78,6 +78,7 @@ public class Main { 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 018e01950..232b849c2 100644 --- a/tools/ahat/src/Menu.java +++ b/tools/ahat/src/Menu.java @@ -27,6 +27,8 @@ class Menu { .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 000000000..17407e1ae --- /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 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 compare + = new Sort.WithPriority( + new Sort.NativeAllocationByHeapName(), + new Sort.NativeAllocationBySize()); + Collections.sort(allocs, compare); + SubsetSelector 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 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 720fcb42f..0dbad7e00 100644 --- a/tools/ahat/src/OverviewHandler.java +++ b/tools/ahat/src/OverviewHandler.java @@ -48,6 +48,22 @@ class OverviewHandler implements AhatHandler { doc.section("Heap Sizes"); printHeapSizes(doc, query); + + List 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 3b7916661..c5f89c315 100644 --- a/tools/ahat/src/Sort.java +++ b/tools/ahat/src/Sort.java @@ -177,5 +177,31 @@ class Sort { 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 { + @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 { + @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 90cd7af2c..701d60ef1 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -19,6 +19,7 @@ import java.io.IOException; 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 @@ public class Main { 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 000000000..7ad4c1d43 --- /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 e8894e260..3291470df 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/test/Tests.java @@ -23,6 +23,7 @@ public class Tests { 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", -- 2.11.0