summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/ahat/README.txt14
-rw-r--r--tools/ahat/src/AhatHandler.java46
-rw-r--r--tools/ahat/src/AhatHttpHandler.java64
-rw-r--r--tools/ahat/src/Doc.java22
-rw-r--r--tools/ahat/src/DominatedList.java105
-rw-r--r--tools/ahat/src/HeapTable.java62
-rw-r--r--tools/ahat/src/Main.java10
-rw-r--r--tools/ahat/src/ObjectHandler.java81
-rw-r--r--tools/ahat/src/ObjectsHandler.java12
-rw-r--r--tools/ahat/src/OverviewHandler.java14
-rw-r--r--tools/ahat/src/RootsHandler.java11
-rw-r--r--tools/ahat/src/SiteHandler.java19
-rw-r--r--tools/ahat/src/SitePrinter.java4
-rw-r--r--tools/ahat/src/SubsetSelector.java109
-rw-r--r--tools/ahat/test-dump/Main.java9
-rw-r--r--tools/ahat/test/PerformanceTest.java55
-rw-r--r--tools/ahat/test/Tests.java3
17 files changed, 420 insertions, 220 deletions
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index d6f55aae16..aa548cca55 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -13,8 +13,6 @@ TODO:
- Recommend how to start looking at a heap dump.
- Say how to enable allocation sites.
- Where to submit feedback, questions, and bug reports.
- * Submit perflib fix for getting stack traces, then uncomment that code in
- AhatSnapshot to use that.
* Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
* Filter out RootObjs in mSnapshot.getGCRoots, not RootsHandler.
* Let user re-sort sites objects info by clicking column headers.
@@ -25,23 +23,15 @@ TODO:
* Show root types.
* Heaped Table
- Make sortable by clicking on headers.
- - Use consistent order for heap columns.
- Sometimes I see "app" first, sometimes last (from one heap dump to
- another) How about, always sort by name?
* For HeapTable with single heap shown, the heap name isn't centered?
* Consistently document functions.
* Should help be part of an AhatHandler, that automatically gets the menu and
stylesheet link rather than duplicating that?
* Show version number with --version.
* Show somewhere where to send bugs.
- * /objects query takes a long time to load without parameters.
* Include a link to /objects in the overview and menu?
* Turn on LOCAL_JAVACFLAGS := -Xlint:unchecked -Werror
* Use hex for object ids in URLs?
- * In general, all tables and descriptions should show a limited amount to
- start, and only show more when requested by the user.
- * Don't have handlers inherit from HttpHandler
- - because they should be independent from http.
* [low priority] by site allocations won't line up if the stack has been
truncated. Is there any way to manually line them up in that case?
@@ -60,8 +50,6 @@ Things to Test:
objects normally sorted by 'app' heap by default.
* Visit /objects without parameters and verify it doesn't throw an exception.
* Visit /objects with an invalid site, verify it doesn't throw an exception.
- * That we can view an array with 3 million elements in a reasonably short
- amount of time (not more than 1 second?)
* That we can view the list of all objects in a reasonably short amount of
time.
* That we don't show the 'extra' column in the DominatedList if we are
@@ -72,8 +60,6 @@ Things to Test:
Reported Issues:
* Request to be able to sort tables by size.
- * Hangs on showing large arrays, where hat does not hang.
- - Solution is probably to not show all the array elements by default.
Perflib Requests:
* Class objects should have java.lang.Class as their class object, not null.
diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/AhatHandler.java
index 2da02f8671..d4b4d1b107 100644
--- a/tools/ahat/src/AhatHandler.java
+++ b/tools/ahat/src/AhatHandler.java
@@ -16,51 +16,17 @@
package com.android.ahat;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
-import java.io.PrintStream;
/**
* AhatHandler.
*
- * Common base class of all the ahat HttpHandlers.
+ * Interface for an ahat page handler.
*/
-abstract class AhatHandler implements HttpHandler {
+interface AhatHandler {
- protected AhatSnapshot mSnapshot;
-
- public AhatHandler(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
- public abstract void handle(Doc doc, Query query) throws IOException;
-
- @Override
- public void handle(HttpExchange exchange) throws IOException {
- exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
- exchange.sendResponseHeaders(200, 0);
- PrintStream ps = new PrintStream(exchange.getResponseBody());
- try {
- HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
- DocString menu = new DocString();
- menu.appendLink(DocString.uri("/"), DocString.text("overview"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("help"), DocString.text("help"));
- doc.menu(menu);
- handle(doc, new Query(exchange.getRequestURI()));
- doc.close();
- } catch (RuntimeException e) {
- // Print runtime exceptions to standard error for debugging purposes,
- // because otherwise they are swallowed and not reported.
- System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
- e.printStackTrace();
- throw e;
- }
- ps.close();
- }
+ /**
+ * Handle the given query, rendering the page to the given document.
+ */
+ void handle(Doc doc, Query query) throws IOException;
}
diff --git a/tools/ahat/src/AhatHttpHandler.java b/tools/ahat/src/AhatHttpHandler.java
new file mode 100644
index 0000000000..0553713702
--- /dev/null
+++ b/tools/ahat/src/AhatHttpHandler.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * AhatHttpHandler.
+ *
+ * HttpHandler for AhatHandlers.
+ */
+class AhatHttpHandler implements HttpHandler {
+
+ private AhatHandler mAhatHandler;
+
+ public AhatHttpHandler(AhatHandler handler) {
+ mAhatHandler = handler;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
+ exchange.sendResponseHeaders(200, 0);
+ PrintStream ps = new PrintStream(exchange.getResponseBody());
+ try {
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+ DocString menu = new DocString();
+ menu.appendLink(DocString.uri("/"), DocString.text("overview"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("help"), DocString.text("help"));
+ doc.menu(menu);
+ mAhatHandler.handle(doc, new Query(exchange.getRequestURI()));
+ doc.close();
+ } catch (RuntimeException e) {
+ // Print runtime exceptions to standard error for debugging purposes,
+ // because otherwise they are swallowed and not reported.
+ System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+ e.printStackTrace();
+ throw e;
+ }
+ ps.close();
+ }
+}
diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/Doc.java
index 7fa70de915..5a70c4c74b 100644
--- a/tools/ahat/src/Doc.java
+++ b/tools/ahat/src/Doc.java
@@ -25,27 +25,27 @@ interface Doc extends AutoCloseable {
/**
* Output the title of the page.
*/
- public void title(String format, Object... args);
+ void title(String format, Object... args);
/**
* Print a line of text for a page menu.
*/
- public void menu(DocString string);
+ void menu(DocString string);
/**
* Start a new section with the given title.
*/
- public void section(String title);
+ void section(String title);
/**
* Print a line of text in a normal font.
*/
- public void println(DocString string);
+ void println(DocString string);
/**
* Print a line of text in a large font that is easy to see and click on.
*/
- public void big(DocString string);
+ void big(DocString string);
/**
* Start a table with the given columns.
@@ -55,7 +55,7 @@ interface Doc extends AutoCloseable {
* This should be followed by calls to the 'row' method to fill in the table
* contents and the 'end' method to end the table.
*/
- public void table(Column... columns);
+ void table(Column... columns);
/**
* Start a table with the following heading structure:
@@ -68,14 +68,14 @@ interface Doc extends AutoCloseable {
* This should be followed by calls to the 'row' method to fill in the table
* contents and the 'end' method to end the table.
*/
- public void table(DocString description, List<Column> subcols, List<Column> cols);
+ void table(DocString description, List<Column> subcols, List<Column> cols);
/**
* Add a row to the currently active table.
* The number of values must match the number of columns provided for the
* currently active table.
*/
- public void row(DocString... values);
+ void row(DocString... values);
/**
* Start a new description list.
@@ -83,15 +83,15 @@ interface Doc extends AutoCloseable {
* This should be followed by calls to description() and finally a call to
* end().
*/
- public void descriptions();
+ void descriptions();
/**
* Add a description to the currently active description list.
*/
- public void description(DocString key, DocString value);
+ void description(DocString key, DocString value);
/**
* End the currently active table or description list.
*/
- public void end();
+ void end();
}
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 123d8be82b..34a5665b3e 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -21,71 +21,35 @@ 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.
+ * @param snapshot the snapshot where the instances reside
+ * @param doc the document to render the dominated list to
+ * @param query the current page query
+ * @param id a unique identifier to use for the dominated list in the current page
+ * @param instances the collection of instances to generate a list for
*/
- public static void render(final AhatSnapshot snapshot, Doc doc,
- Collection<Instance> instances, Query query) {
+ public static void render(final AhatSnapshot snapshot,
+ Doc doc, Query query, String id, Collection<Instance> instances) {
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());
- }
+ HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts);
}
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) {
+ public TableConfig(AhatSnapshot snapshot) {
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
@@ -95,9 +59,6 @@ class DominatedList {
@Override
public long getSize(Instance element, Heap heap) {
- if (element == null) {
- return mHiddenSizes.get(heap);
- }
int index = mSnapshot.getHeapIndex(heap);
return element.getRetainedSize(index);
}
@@ -110,56 +71,10 @@ class DominatedList {
}
public DocString render(Instance element) {
- if (element == null) {
- return DocString.text("...");
- } else {
- return Value.render(element);
- }
+ 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.appendFormat("(%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);
- }
}
-
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
index 37d58164a8..ed11d1724a 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.java
@@ -18,7 +18,9 @@ package com.android.ahat;
import com.android.tools.perflib.heap.Heap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Class for rendering a table that includes sizes of some kind for each heap.
@@ -27,22 +29,27 @@ class HeapTable {
/**
* Configuration for a value column of a heap table.
*/
- public static interface ValueConfig<T> {
- public String getDescription();
- public DocString render(T element);
+ public interface ValueConfig<T> {
+ String getDescription();
+ DocString render(T element);
}
/**
* Configuration for the HeapTable.
*/
- public static interface TableConfig<T> {
- public String getHeapsDescription();
- public long getSize(T element, Heap heap);
- public List<ValueConfig<T>> getValueConfigs();
+ public interface TableConfig<T> {
+ String getHeapsDescription();
+ long getSize(T element, Heap heap);
+ List<ValueConfig<T>> getValueConfigs();
}
- public static <T> void render(Doc doc, TableConfig<T> config,
- AhatSnapshot snapshot, List<T> elements) {
+ /**
+ * Render the table to the given document.
+ * @param query - The page query.
+ * @param id - A unique identifier for the table on the page.
+ */
+ public static <T> void render(Doc doc, Query query, String id,
+ TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
// Only show the heaps that have non-zero entries.
List<Heap> heaps = new ArrayList<Heap>();
for (Heap heap : snapshot.getHeaps()) {
@@ -68,9 +75,10 @@ class HeapTable {
}
doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);
- // Print the entries.
+ // Print the entries up to the selected limit.
+ SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
ArrayList<DocString> vals = new ArrayList<DocString>();
- for (T elem : elements) {
+ for (T elem : selector.selected()) {
vals.clear();
long total = 0;
for (Heap heap : heaps) {
@@ -87,7 +95,39 @@ class HeapTable {
}
doc.row(vals.toArray(new DocString[0]));
}
+
+ // Print a summary of the remaining entries if there are any.
+ List<T> remaining = selector.remaining();
+ if (!remaining.isEmpty()) {
+ Map<Heap, Long> summary = new HashMap<Heap, Long>();
+ for (Heap heap : heaps) {
+ summary.put(heap, 0L);
+ }
+
+ for (T elem : remaining) {
+ for (Heap heap : heaps) {
+ summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
+ }
+ }
+
+ vals.clear();
+ long total = 0;
+ for (Heap heap : heaps) {
+ long size = summary.get(heap);
+ total += size;
+ vals.add(DocString.format("%,14d", size));
+ }
+ if (showTotal) {
+ vals.add(DocString.format("%,14d", total));
+ }
+
+ for (ValueConfig<T> value : values) {
+ vals.add(DocString.text("..."));
+ }
+ doc.row(vals.toArray(new DocString[0]));
+ }
doc.end();
+ selector.render(doc);
}
// Returns true if the given heap has a non-zero size entry.
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 1563aa0262..96fc53b6bd 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -73,11 +73,11 @@ public class Main {
InetAddress loopback = InetAddress.getLoopbackAddress();
InetSocketAddress addr = new InetSocketAddress(loopback, port);
HttpServer server = HttpServer.create(addr, 0);
- server.createContext("/", new OverviewHandler(ahat, hprof));
- server.createContext("/roots", new RootsHandler(ahat));
- server.createContext("/object", new ObjectHandler(ahat));
- server.createContext("/objects", new ObjectsHandler(ahat));
- server.createContext("/site", new SiteHandler(ahat));
+ server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof)));
+ server.createContext("/roots", new AhatHttpHandler(new RootsHandler(ahat)));
+ 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("/bitmap", new BitmapHandler(ahat));
server.createContext("/help", new StaticHandler("help.html", "text/html"));
server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 5e321e267e..9e4ce563d5 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -25,13 +25,26 @@ 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-class ObjectHandler extends AhatHandler {
+class ObjectHandler implements AhatHandler {
+
+ private static final String ARRAY_ELEMENTS_ID = "elements";
+ private static final String DOMINATOR_PATH_ID = "dompath";
+ private static final String ALLOCATION_SITE_ID = "frames";
+ private static final String DOMINATED_OBJECTS_ID = "dominated";
+ private static final String INSTANCE_FIELDS_ID = "ifields";
+ private static final String STATIC_FIELDS_ID = "sfields";
+ private static final String HARD_REFS_ID = "refs";
+ private static final String SOFT_REFS_ID = "srefs";
+
+ private AhatSnapshot mSnapshot;
+
public ObjectHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -46,8 +59,8 @@ class ObjectHandler extends AhatHandler {
doc.title("Object %08x", inst.getUniqueId());
doc.big(Value.render(inst));
- printAllocationSite(doc, inst);
- printDominatorPath(doc, inst);
+ printAllocationSite(doc, query, inst);
+ printDominatorPath(doc, query, inst);
doc.section("Object Info");
ClassObj cls = inst.getClassObj();
@@ -62,39 +75,46 @@ class ObjectHandler extends AhatHandler {
printBitmap(doc, inst);
if (inst instanceof ClassInstance) {
- printClassInstanceFields(doc, (ClassInstance)inst);
+ printClassInstanceFields(doc, query, (ClassInstance)inst);
} else if (inst instanceof ArrayInstance) {
- printArrayElements(doc, (ArrayInstance)inst);
+ printArrayElements(doc, query, (ArrayInstance)inst);
} else if (inst instanceof ClassObj) {
- printClassInfo(doc, (ClassObj)inst);
+ printClassInfo(doc, query, (ClassObj)inst);
}
- printReferences(doc, inst);
+ printReferences(doc, query, inst);
printDominatedObjects(doc, query, inst);
}
- private static void printClassInstanceFields(Doc doc, ClassInstance inst) {
+ private static void printClassInstanceFields(Doc doc, Query query, ClassInstance inst) {
doc.section("Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- for (ClassInstance.FieldValue field : inst.getValues()) {
+ SubsetSelector<ClassInstance.FieldValue> selector
+ = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues());
+ for (ClassInstance.FieldValue field : selector.selected()) {
doc.row(
DocString.text(field.getField().getType().toString()),
DocString.text(field.getField().getName()),
Value.render(field.getValue()));
}
doc.end();
+ selector.render(doc);
}
- private static void printArrayElements(Doc doc, ArrayInstance array) {
+ private static void printArrayElements(Doc doc, Query query, 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.format("%d", i), Value.render(elements[i]));
+ List<Object> elements = Arrays.asList(array.getValues());
+ SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
+ int i = 0;
+ for (Object elem : selector.selected()) {
+ doc.row(DocString.format("%d", i), Value.render(elem));
+ i++;
}
doc.end();
+ selector.render(doc);
}
- private static void printClassInfo(Doc doc, ClassObj clsobj) {
+ private static void printClassInfo(Doc doc, Query query, ClassObj clsobj) {
doc.section("Class Info");
doc.descriptions();
doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj()));
@@ -103,41 +123,52 @@ class ObjectHandler extends AhatHandler {
doc.section("Static Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) {
+ List<Map.Entry<Field, Object>> fields
+ = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet());
+ SubsetSelector<Map.Entry<Field, Object>> selector
+ = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+ for (Map.Entry<Field, Object> field : selector.selected()) {
doc.row(
DocString.text(field.getKey().getType().toString()),
DocString.text(field.getKey().getName()),
Value.render(field.getValue()));
}
doc.end();
+ selector.render(doc);
}
- private static void printReferences(Doc doc, Instance inst) {
+ private static void printReferences(Doc doc, Query query, 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()) {
+ List<Instance> references = inst.getHardReferences();
+ SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
+ for (Instance ref : selector.selected()) {
doc.row(Value.render(ref));
}
doc.end();
+ selector.render(doc);
}
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));
+ List<Instance> references = inst.getSoftReferences();
+ SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+ for (Instance ref : selector.selected()) {
+ doc.row(Value.render(ref));
}
doc.end();
+ selector.render(doc);
}
}
- private void printAllocationSite(Doc doc, Instance inst) {
+ private void printAllocationSite(Doc doc, Query query, Instance inst) {
doc.section("Allocation Site");
Site site = mSnapshot.getSiteForInstance(inst);
- SitePrinter.printSite(doc, mSnapshot, site);
+ SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
}
// Draw the bitmap corresponding to this instance if there is one.
@@ -150,7 +181,7 @@ class ObjectHandler extends AhatHandler {
}
}
- private void printDominatorPath(Doc doc, Instance inst) {
+ private void printDominatorPath(Doc doc, Query query, Instance inst) {
doc.section("Dominator Path from Root");
List<Instance> path = new ArrayList<Instance>();
for (Instance parent = inst;
@@ -193,14 +224,14 @@ class ObjectHandler extends AhatHandler {
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, mSnapshot, path);
+ HeapTable.render(doc, query, DOMINATOR_PATH_ID, 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);
+ DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
} else {
doc.println(DocString.text("(none)"));
}
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 4e9c42e566..8ad3f481da 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -22,9 +22,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-class ObjectsHandler extends AhatHandler {
+class ObjectsHandler implements AhatHandler {
+ private static final String OBJECTS_ID = "objects";
+
+ private AhatSnapshot mSnapshot;
+
public ObjectsHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -51,13 +55,15 @@ class ObjectsHandler extends AhatHandler {
new Column("Size", Column.Align.RIGHT),
new Column("Heap"),
new Column("Object"));
- for (Instance inst : insts) {
+ SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+ for (Instance inst : selector.selected()) {
doc.row(
DocString.format("%,d", inst.getSize()),
DocString.text(inst.getHeap().getName()),
Value.render(inst));
}
doc.end();
+ selector.render(doc);
}
}
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index f49c009b30..e86679f378 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -22,11 +22,15 @@ import java.io.File;
import java.util.Collections;
import java.util.List;
-class OverviewHandler extends AhatHandler {
+class OverviewHandler implements AhatHandler {
+
+ private static final String OVERVIEW_ID = "overview";
+
+ private AhatSnapshot mSnapshot;
private File mHprof;
public OverviewHandler(AhatSnapshot snapshot, File hprof) {
- super(snapshot);
+ mSnapshot = snapshot;
mHprof = hprof;
}
@@ -43,7 +47,7 @@ class OverviewHandler extends AhatHandler {
doc.end();
doc.section("Heap Sizes");
- printHeapSizes(doc);
+ printHeapSizes(doc, query);
DocString menu = new DocString();
menu.appendLink(DocString.uri("roots"), DocString.text("Roots"));
@@ -54,7 +58,7 @@ class OverviewHandler extends AhatHandler {
doc.big(menu);
}
- private void printHeapSizes(Doc doc) {
+ private void printHeapSizes(Doc doc, Query query) {
List<Object> dummy = Collections.singletonList(null);
HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
@@ -70,7 +74,7 @@ class OverviewHandler extends AhatHandler {
return Collections.emptyList();
}
};
- HeapTable.render(doc, table, mSnapshot, dummy);
+ HeapTable.render(doc, query, OVERVIEW_ID, table, mSnapshot, dummy);
}
}
diff --git a/tools/ahat/src/RootsHandler.java b/tools/ahat/src/RootsHandler.java
index 185b9bf24f..2a92c90c4e 100644
--- a/tools/ahat/src/RootsHandler.java
+++ b/tools/ahat/src/RootsHandler.java
@@ -24,9 +24,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-class RootsHandler extends AhatHandler {
+class RootsHandler implements AhatHandler {
+
+ private static final String ROOTS_ID = "roots";
+
+ private AhatSnapshot mSnapshot;
+
public RootsHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -45,7 +50,7 @@ class RootsHandler extends AhatHandler {
for (Instance inst : rootset) {
roots.add(inst);
}
- DominatedList.render(mSnapshot, doc, roots, query);
+ DominatedList.render(mSnapshot, doc, query, ROOTS_ID, roots);
}
}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 0a9381ef24..0425a5a825 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -22,9 +22,15 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-class SiteHandler extends AhatHandler {
+class SiteHandler implements AhatHandler {
+ private static final String ALLOCATION_SITE_ID = "frames";
+ private static final String SITES_CALLED_ID = "called";
+ private static final String OBJECTS_ALLOCATED_ID = "objects";
+
+ private AhatSnapshot mSnapshot;
+
public SiteHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -35,7 +41,7 @@ class SiteHandler extends AhatHandler {
doc.title("Site %s", site.getName());
doc.section("Allocation Site");
- SitePrinter.printSite(doc, mSnapshot, site);
+ SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
doc.section("Sites Called from Here");
List<Site> children = site.getChildren();
@@ -69,7 +75,7 @@ class SiteHandler extends AhatHandler {
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, mSnapshot, children);
+ HeapTable.render(doc, query, SITES_CALLED_ID, table, mSnapshot, children);
}
doc.section("Objects Allocated");
@@ -84,7 +90,9 @@ class SiteHandler extends AhatHandler {
new Sort.ObjectsInfoBySize(),
new Sort.ObjectsInfoByClassName());
Collections.sort(infos, compare);
- for (Site.ObjectsInfo info : infos) {
+ SubsetSelector<Site.ObjectsInfo> selector
+ = new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
+ for (Site.ObjectsInfo info : selector.selected()) {
String className = AhatSnapshot.getClassName(info.classObj);
doc.row(
DocString.format("%,14d", info.numBytes),
@@ -96,6 +104,7 @@ class SiteHandler extends AhatHandler {
Value.render(info.classObj));
}
doc.end();
+ selector.render(doc);
}
}
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
index be87032a2c..2c06b47a29 100644
--- a/tools/ahat/src/SitePrinter.java
+++ b/tools/ahat/src/SitePrinter.java
@@ -22,7 +22,7 @@ import java.util.Collections;
import java.util.List;
class SitePrinter {
- public static void printSite(Doc doc, AhatSnapshot snapshot, Site site) {
+ public static void printSite(AhatSnapshot snapshot, Doc doc, Query query, String id, Site site) {
List<Site> path = new ArrayList<Site>();
for (Site parent = site; parent != null; parent = parent.getParent()) {
path.add(parent);
@@ -60,6 +60,6 @@ class SitePrinter {
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, snapshot, path);
+ HeapTable.render(doc, query, id, table, snapshot, path);
}
}
diff --git a/tools/ahat/src/SubsetSelector.java b/tools/ahat/src/SubsetSelector.java
new file mode 100644
index 0000000000..79399c178b
--- /dev/null
+++ b/tools/ahat/src/SubsetSelector.java
@@ -0,0 +1,109 @@
+/*
+ * 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.util.List;
+
+/**
+ * The SubsetSelector is that can be added to a page that lets the
+ * user select a limited number of elements to show.
+ * This is used to limit the number of elements shown on a page by default,
+ * requiring the user to explicitly request more, so users not interested in
+ * more don't have to wait for everything to render.
+ */
+class SubsetSelector<T> {
+ private static final int kIncrAmount = 1000;
+ private static final int kDefaultShown = 1000;
+
+ private Query mQuery;
+ private String mId;
+ private int mLimit;
+ private List<T> mElements;
+
+ /**
+ * @param id - the name of the query parameter key that should hold
+ * the limit selectors selected value.
+ * @param query - The query for the current page. This is required so the
+ * LimitSelector can add a link to the same page with modified limit
+ * selection.
+ * @param elements - the elements to select from. The collection of elements
+ * should not be modified during the lifetime of the SubsetSelector object.
+ */
+ public SubsetSelector(Query query, String id, List<T> elements) {
+ mQuery = query;
+ mId = id;
+ mLimit = getSelectedLimit(query, id, elements.size());
+ mElements = elements;
+ }
+
+ // Return the list of elements included in the selected subset.
+ public List<T> selected() {
+ return mElements.subList(0, mLimit);
+ }
+
+ // Return the list of remaining elements not included in the selected subset.
+ public List<T> remaining() {
+ return mElements.subList(mLimit, mElements.size());
+ }
+
+ /**
+ * Returns the currently selected limit.
+ * @param query the current page query
+ * @param size the total number of elements to select from
+ * @return the number of selected elements
+ */
+ private static int getSelectedLimit(Query query, String id, int size) {
+ String value = query.get(id, null);
+ try {
+ int ivalue = Math.min(size, Integer.parseInt(value));
+ return Math.max(0, ivalue);
+ } catch (NumberFormatException e) {
+ // We can't parse the value as a number. Ignore it.
+ }
+ return Math.min(kDefaultShown, size);
+ }
+
+ // Render the limit selector to the given doc.
+ // It has the form:
+ // (showing X of Y - show none - show less - show more - show all)
+ public void render(Doc doc) {
+ int all = mElements.size();
+ if (all > kDefaultShown) {
+ DocString menu = new DocString();
+ menu.appendFormat("(%d of %d elements shown - ", mLimit, all);
+ if (mLimit > 0) {
+ int less = Math.max(0, mLimit - kIncrAmount);
+ menu.appendLink(mQuery.with(mId, 0), DocString.text("show none"));
+ menu.append(" - ");
+ menu.appendLink(mQuery.with(mId, less), DocString.text("show less"));
+ menu.append(" - ");
+ } else {
+ menu.append("show none - show less - ");
+ }
+ if (mLimit < all) {
+ int more = Math.min(mLimit + kIncrAmount, all);
+ menu.appendLink(mQuery.with(mId, more), DocString.text("show more"));
+ menu.append(" - ");
+ menu.appendLink(mQuery.with(mId, all), DocString.text("show all"));
+ menu.append(")");
+ } else {
+ menu.append("show more - show all)");
+ }
+ doc.println(menu);
+ }
+ }
+}
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 7b8774a34d..90cd7af2cc 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -39,6 +39,15 @@ public class Main {
public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
+ public byte[] bigArray;
+
+ DumpedStuff() {
+ int N = 1000000;
+ bigArray = new byte[N];
+ for (int i = 0; i < N; i++) {
+ bigArray[i] = (byte)((i*i) & 0xFF);
+ }
+ }
}
public static void main(String[] args) throws IOException {
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
new file mode 100644
index 0000000000..6e46800603
--- /dev/null
+++ b/tools/ahat/test/PerformanceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Instance;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class PerformanceTest {
+ private static class NullOutputStream extends OutputStream {
+ public void write(int b) throws IOException {
+ }
+ }
+
+ @Test
+ public void bigArray() throws IOException {
+ // It should not take more than 1 second to load the default object view
+ // for any object, including big arrays.
+ TestDump dump = TestDump.getTestDump();
+
+ Instance bigArray = (Instance)dump.getDumpedThing("bigArray");
+ assertNotNull(bigArray);
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ PrintStream ps = new PrintStream(new NullOutputStream());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("bigArray test"), DocString.uri("style.css"));
+ String uri = "http://localhost:7100/object?id=" + bigArray.getId();
+ Query query = new Query(DocString.uri(uri));
+
+ long start = System.currentTimeMillis();
+ handler.handle(doc, query);
+ long time = System.currentTimeMillis() - start;
+ assertTrue("bigArray took too long: " + time + "ms", time < 1000);
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index bab712199c..e8894e2603 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -23,8 +23,9 @@ public class Tests {
if (args.length == 0) {
args = new String[]{
"com.android.ahat.InstanceUtilsTest",
+ "com.android.ahat.PerformanceTest",
"com.android.ahat.QueryTest",
- "com.android.ahat.SortTest"
+ "com.android.ahat.SortTest",
};
}
JUnitCore.main(args);