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",