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/DominatedList.java b/tools/ahat/src/DominatedList.java
new file mode 100644
index 0000000..53d1073
--- /dev/null
+++ b/tools/ahat/src/DominatedList.java
@@ -0,0 +1,165 @@
+/*
+ * 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.Heap;
+import com.android.tools.perflib.heap.Instance;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for rendering a list of instances dominated by a single instance in a
+ * pretty way.
+ */
+class DominatedList {
+  private static final int kIncrAmount = 100;
+  private static final int kDefaultShown = 100;
+
+  /**
+   * Render a table to the given HtmlWriter showing a pretty list of
+   * instances.
+   *
+   * Rather than show all of the instances (which may be very many), we use
+   * the query parameter "dominated" to specify a limited number of
+   * instances to show. The 'uri' parameter should be the current page URI, so
+   * that we can add links to "show more" and "show less" objects that go to
+   * the same page with only the number of objects adjusted.
+   */
+  public static void render(final AhatSnapshot snapshot, Doc doc,
+      Collection<Instance> instances, Query query) {
+    List<Instance> insts = new ArrayList<Instance>(instances);
+    Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
+
+    int numInstancesToShow = getNumInstancesToShow(query, insts.size());
+    List<Instance> shown = new ArrayList<Instance>(insts.subList(0, numInstancesToShow));
+    List<Instance> hidden = insts.subList(numInstancesToShow, insts.size());
+
+    // Add 'null' as a marker for "all the rest of the objects".
+    if (!hidden.isEmpty()) {
+      shown.add(null);
+    }
+    HeapTable.render(doc, new TableConfig(snapshot, hidden), snapshot, shown);
+
+    if (insts.size() > kDefaultShown) {
+      printMenu(doc, query, numInstancesToShow, insts.size());
+    }
+  }
+
+  private static class TableConfig implements HeapTable.TableConfig<Instance> {
+    AhatSnapshot mSnapshot;
+
+    // Map from heap name to the total size of the instances not shown in the
+    // table.
+    Map<Heap, Long> mHiddenSizes;
+
+    public TableConfig(AhatSnapshot snapshot, List<Instance> hidden) {
+      mSnapshot = snapshot;
+      mHiddenSizes = new HashMap<Heap, Long>();
+      for (Heap heap : snapshot.getHeaps()) {
+        mHiddenSizes.put(heap, 0L);
+      }
+
+      if (!hidden.isEmpty()) {
+        for (Instance inst : hidden) {
+          for (Heap heap : snapshot.getHeaps()) {
+            int index = snapshot.getHeapIndex(heap);
+            long size = inst.getRetainedSize(index);
+            mHiddenSizes.put(heap, mHiddenSizes.get(heap) + size);
+          }
+        }
+      }
+    }
+
+    @Override
+    public String getHeapsDescription() {
+      return "Bytes Retained by Heap";
+    }
+
+    @Override
+    public long getSize(Instance element, Heap heap) {
+      if (element == null) {
+        return mHiddenSizes.get(heap);
+      }
+      int index = mSnapshot.getHeapIndex(heap);
+      return element.getRetainedSize(index);
+    }
+
+    @Override
+    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.text("...");
+          } else {
+            return Value.render(element);
+          }
+        }
+      };
+      return Collections.singletonList(value);
+    }
+  }
+
+  // Figure out how many objects to show based on the query parameter.
+  // The resulting value is guaranteed to be at least zero, and no greater
+  // than the number of total objects.
+  private static int getNumInstancesToShow(Query query, int totalNumInstances) {
+    String value = query.get("dominated", null);
+    try {
+      int count = Math.min(totalNumInstances, Integer.parseInt(value));
+      return Math.max(0, count);
+    } catch (NumberFormatException e) {
+      // We can't parse the value as a number. Ignore it.
+    }
+    return Math.min(kDefaultShown, totalNumInstances);
+  }
+
+  // Print a menu line after the table to control how many objects are shown.
+  // It has the form:
+  //  (showing X of Y objects - show none - show less - show more - show all)
+  private static void printMenu(Doc doc, Query query, int shown, int all) {
+    DocString menu = new DocString();
+    menu.append("(%d of %d objects shown - ", shown, all);
+    if (shown > 0) {
+      int less = Math.max(0, shown - kIncrAmount);
+      menu.appendLink(query.with("dominated", 0), DocString.text("show none"));
+      menu.append(" - ");
+      menu.appendLink(query.with("dominated", less), DocString.text("show less"));
+      menu.append(" - ");
+    } else {
+      menu.append("show none - show less - ");
+    }
+    if (shown < all) {
+      int more = Math.min(shown + kIncrAmount, all);
+      menu.appendLink(query.with("dominated", more), DocString.text("show more"));
+      menu.append(" - ");
+      menu.appendLink(query.with("dominated", all), DocString.text("show all"));
+      menu.append(")");
+    } else {
+      menu.append("show more - show all)");
+    }
+    doc.println(menu);
+  }
+}
+