ahat - An android heap dump viewer. Initial checkin.

ahat is an android-aware heap dump viewer based on perflib with a
simple html interface.

Change-Id: I7c18a7603dbbe735f778a95cd047f4f9ec1705ef
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
new file mode 100644
index 0000000..eecd7d1
--- /dev/null
+++ b/tools/ahat/src/ObjectHandler.java
@@ -0,0 +1,209 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+class ObjectHandler extends AhatHandler {
+  public ObjectHandler(AhatSnapshot snapshot) {
+    super(snapshot);
+  }
+
+  @Override
+  public void handle(Doc doc, Query query) throws IOException {
+    long id = query.getLong("id", 0);
+    Instance inst = mSnapshot.findInstance(id);
+    if (inst == null) {
+      doc.println(DocString.text("No object with id %08xl", id));
+      return;
+    }
+
+    doc.title("Object %08x", inst.getUniqueId());
+    doc.big(Value.render(inst));
+
+    printAllocationSite(doc, inst);
+    printDominatorPath(doc, inst);
+
+    doc.section("Object Info");
+    ClassObj cls = inst.getClassObj();
+    doc.descriptions();
+    doc.description(DocString.text("Class"), Value.render(cls));
+    doc.description(DocString.text("Size"), DocString.text("%d", inst.getSize()));
+    doc.description(
+        DocString.text("Retained Size"),
+        DocString.text("%d", inst.getTotalRetainedSize()));
+    doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
+    doc.end();
+
+    printBitmap(doc, inst);
+    if (inst instanceof ClassInstance) {
+      printClassInstanceFields(doc, (ClassInstance)inst);
+    } else if (inst instanceof ArrayInstance) {
+      printArrayElements(doc, (ArrayInstance)inst);
+    } else if (inst instanceof ClassObj) {
+      printClassInfo(doc, (ClassObj)inst);
+    }
+    printReferences(doc, inst);
+    printDominatedObjects(doc, query, inst);
+  }
+
+  private static void printClassInstanceFields(Doc doc, ClassInstance inst) {
+    doc.section("Fields");
+    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+    for (ClassInstance.FieldValue field : inst.getValues()) {
+      doc.row(
+          DocString.text(field.getField().getType().toString()),
+          DocString.text(field.getField().getName()),
+          Value.render(field.getValue()));
+    }
+    doc.end();
+  }
+
+  private static void printArrayElements(Doc doc, ArrayInstance array) {
+    doc.section("Array Elements");
+    doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
+    Object[] elements = array.getValues();
+    for (int i = 0; i < elements.length; i++) {
+      doc.row(DocString.text("%d", i), Value.render(elements[i]));
+    }
+    doc.end();
+  }
+
+  private static void printClassInfo(Doc doc, 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.end();
+
+    doc.section("Static Fields");
+    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+    for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) {
+      doc.row(
+          DocString.text(field.getKey().getType().toString()),
+          DocString.text(field.getKey().getName()),
+          Value.render(field.getValue()));
+    }
+    doc.end();
+  }
+
+  private static void printReferences(Doc doc, Instance inst) {
+    doc.section("Objects with References to this Object");
+    if (inst.getHardReferences().isEmpty()) {
+      doc.println(DocString.text("(none)"));
+    } else {
+      doc.table(new Column("Object"));
+      for (Instance ref : inst.getHardReferences()) {
+        doc.row(Value.render(ref));
+      }
+      doc.end();
+    }
+
+    if (inst.getSoftReferences() != null) {
+      doc.section("Objects with Soft References to this Object");
+      doc.table(new Column("Object"));
+      for (Instance ref : inst.getSoftReferences()) {
+        doc.row(Value.render(inst));
+      }
+      doc.end();
+    }
+  }
+
+  private void printAllocationSite(Doc doc, Instance inst) {
+    doc.section("Allocation Site");
+    Site site = mSnapshot.getSiteForInstance(inst);
+    SitePrinter.printSite(doc, mSnapshot, 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);
+    if (bitmap != null) {
+      doc.section("Bitmap Image");
+      doc.println(DocString.image(
+            DocString.uri("bitmap?id=%d", bitmap.getId()), "bitmap image"));
+    }
+  }
+
+  private void printDominatorPath(Doc doc, Instance inst) {
+    doc.section("Dominator Path from Root");
+    List<Instance> path = new ArrayList<Instance>();
+    for (Instance parent = inst;
+        parent != null && !(parent instanceof RootObj);
+        parent = parent.getImmediateDominator()) {
+      path.add(parent);
+    }
+
+    // Add 'null' as a marker for the root.
+    path.add(null);
+    Collections.reverse(path);
+
+    HeapTable.TableConfig<Instance> table = new HeapTable.TableConfig<Instance>() {
+      public String getHeapsDescription() {
+        return "Bytes Retained by Heap";
+      }
+
+      public long getSize(Instance element, Heap heap) {
+        if (element == null) {
+          return mSnapshot.getHeapSize(heap);
+        }
+        int index = mSnapshot.getHeapIndex(heap);
+        return element.getRetainedSize(index);
+      }
+
+      public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
+        HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+          public String getDescription() {
+            return "Object";
+          }
+
+          public DocString render(Instance element) {
+            if (element == null) {
+              return DocString.link(DocString.uri("roots"), DocString.text("ROOT"));
+            } else {
+              return DocString.text("→ ").append(Value.render(element));
+            }
+          }
+        };
+        return Collections.singletonList(value);
+      }
+    };
+    HeapTable.render(doc, table, mSnapshot, path);
+  }
+
+  public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+    doc.section("Immediately Dominated Objects");
+    List<Instance> instances = mSnapshot.getDominated(inst);
+    if (instances != null) {
+      DominatedList.render(mSnapshot, doc, instances, query);
+    } else {
+      doc.println(DocString.text("(none)"));
+    }
+  }
+}
+