diff options
author | 2018-09-06 15:18:50 +0000 | |
---|---|---|
committer | 2018-09-06 15:18:50 +0000 | |
commit | 9c568f10f68cdbeb285e41cb4538c06f5e90c1b2 (patch) | |
tree | 6993007e446696412324fde89ccbc86c1cca180e | |
parent | 0c5387a781b707ed5d95f239c1b4caad17316599 (diff) | |
parent | d86d281b0468c389789b72e275755e32e196fc93 (diff) |
Merge "Add option to view all instances of a class."
9 files changed, 199 insertions, 41 deletions
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt index 5426f7b866..7aa994a805 100644 --- a/tools/ahat/etc/ahat_api.txt +++ b/tools/ahat/etc/ahat_api.txt @@ -96,6 +96,7 @@ package com.android.ahat.heapdump { method public boolean isArrayInstance(); method public boolean isClassInstance(); method public boolean isClassObj(); + method public boolean isInstanceOfClass(java.lang.String); method public boolean isPlaceHolder(); method public boolean isRoot(); method public boolean isStronglyReachable(); @@ -226,6 +227,7 @@ package com.android.ahat.heapdump { method public int getLineNumber(); method public java.lang.String getMethodName(); method public void getObjects(java.lang.String, java.lang.String, java.util.Collection<com.android.ahat.heapdump.AhatInstance>); + method public void getObjects(java.util.function.Predicate<com.android.ahat.heapdump.AhatInstance>, java.util.function.Consumer<com.android.ahat.heapdump.AhatInstance>); method public java.util.List<com.android.ahat.heapdump.Site.ObjectsInfo> getObjectsInfos(); method public com.android.ahat.heapdump.Site getParent(); method public java.lang.String getSignature(); diff --git a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java index 1a8f018bd5..81611b6c72 100644 --- a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java +++ b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java @@ -16,6 +16,7 @@ package com.android.ahat; +import com.android.ahat.heapdump.AhatHeap; import com.android.ahat.heapdump.AhatInstance; import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.Site; @@ -24,6 +25,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Predicate; class ObjectsHandler implements AhatHandler { private static final String OBJECTS_ID = "objects"; @@ -34,32 +36,102 @@ class ObjectsHandler implements AhatHandler { mSnapshot = snapshot; } + /** + * Get the list of instances that match the given site, class, and heap + * filters. This method is public to facilitate testing. + * + * @param site the site to get instances from + * @param className non-null name of the class to restrict instances to. + * @param subclass if true, include instances of subclasses of the named class. + * @param heapName name of the heap to restrict instances to. May be null to + * allow instances on any heap. + * @return list of matching instances + */ + public static List<AhatInstance> getObjects( + Site site, String className, boolean subclass, String heapName) { + Predicate<AhatInstance> predicate = (x) -> { + return (heapName == null || x.getHeap().getName().equals(heapName)) + && (subclass ? x.isInstanceOfClass(className) : className.equals(x.getClassName())); + }; + + List<AhatInstance> insts = new ArrayList<AhatInstance>(); + site.getObjects(predicate, x -> insts.add(x)); + return insts; + } + @Override public void handle(Doc doc, Query query) throws IOException { int id = query.getInt("id", 0); - String className = query.get("class", null); + String className = query.get("class", "java.lang.Object"); String heapName = query.get("heap", null); + boolean subclass = (query.getInt("subclass", 0) != 0); Site site = mSnapshot.getSite(id); - List<AhatInstance> insts = new ArrayList<AhatInstance>(); - site.getObjects(heapName, className, insts); + List<AhatInstance> insts = getObjects(site, className, subclass, heapName); Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot)); - doc.title("Objects"); + doc.title("Instances"); + + // Write a description of the current settings, with links to adjust the + // settings, such as: + // Site: ROOT - + // Class: android.os.Binder + // Subclasses: excluded (switch to included) + // Heap: any (switch to app, image, zygote) + // Count: 17,424 + doc.descriptions(); + doc.description(DocString.text("Site"), Summarizer.summarize(site)); + doc.description(DocString.text("Class"), DocString.text(className)); + + DocString subclassChoice = DocString.text(subclass ? "included" : "excluded"); + subclassChoice.append(" (switch to "); + subclassChoice.appendLink(query.with("subclass", subclass ? 0 : 1), + DocString.text(subclass ? "excluded" : "included")); + subclassChoice.append(")"); + doc.description(DocString.text("Subclasses"), subclassChoice); + + DocString heapChoice = DocString.text(heapName == null ? "any" : heapName); + heapChoice.append(" (switch to "); + String comma = ""; + for (AhatHeap heap : mSnapshot.getHeaps()) { + if (!heap.getName().equals(heapName)) { + heapChoice.append(comma); + heapChoice.appendLink( + query.with("heap", heap.getName()), + DocString.text(heap.getName())); + comma = ", "; + } + } + if (heapName != null) { + heapChoice.append(comma); + heapChoice.appendLink( + query.with("heap", null), + DocString.text("any")); + } + heapChoice.append(")"); + doc.description(DocString.text("Heap"), heapChoice); + + doc.description(DocString.text("Count"), DocString.format("%,14d", insts.size())); + doc.end(); + doc.println(DocString.text("")); - SizeTable.table(doc, mSnapshot.isDiffed(), - new Column("Heap"), - new Column("Object")); + if (insts.isEmpty()) { + doc.println(DocString.text("(none)")); + } else { + SizeTable.table(doc, mSnapshot.isDiffed(), + new Column("Heap"), + new Column("Object")); - SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts); - for (AhatInstance inst : selector.selected()) { - AhatInstance base = inst.getBaseline(); - SizeTable.row(doc, inst.getSize(), base.getSize(), - DocString.text(inst.getHeap().getName()), - Summarizer.summarize(inst)); + SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts); + for (AhatInstance inst : selector.selected()) { + AhatInstance base = inst.getBaseline(); + SizeTable.row(doc, inst.getSize(), base.getSize(), + DocString.text(inst.getHeap().getName()), + Summarizer.summarize(inst)); + } + SizeTable.end(doc); + selector.render(doc); } - SizeTable.end(doc); - selector.render(doc); } } diff --git a/tools/ahat/src/main/com/android/ahat/Query.java b/tools/ahat/src/main/com/android/ahat/Query.java index 9c2783c081..5f064cdada 100644 --- a/tools/ahat/src/main/com/android/ahat/Query.java +++ b/tools/ahat/src/main/com/android/ahat/Query.java @@ -79,7 +79,9 @@ class Query { /** * Return a uri suitable for an href target that links to the current * page, except with the named query parameter set to the new value. - * + * <p> + * <code>value</code> may be null to remove the named query parameter. + * <p> * The generated parameters will be sorted alphabetically so it is easier to * test. */ @@ -92,11 +94,13 @@ class Query { params.put(name, value); String and = ""; for (Map.Entry<String, String> entry : params.entrySet()) { - newQuery.append(and); - newQuery.append(entry.getKey()); - newQuery.append('='); - newQuery.append(entry.getValue()); - and = "&"; + if (entry.getValue() != null) { + newQuery.append(and); + newQuery.append(entry.getKey()); + newQuery.append('='); + newQuery.append(entry.getValue()); + and = "&"; + } } return DocString.uri(newQuery.toString()); } diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java index 4c60d8b2c8..0511798cad 100644 --- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java @@ -107,21 +107,6 @@ public class AhatClassInstance extends AhatInstance { return new ReferenceIterator(); } - /** - * Returns true if this is an instance of a (subclass of a) class with the - * given name. - */ - private boolean isInstanceOfClass(String className) { - AhatClassObj cls = getClassObj(); - while (cls != null) { - if (className.equals(cls.getName())) { - return true; - } - cls = cls.getSuperClassObj(); - } - return false; - } - @Override public String asString(int maxChars) { if (!isInstanceOfClass("java.lang.String")) { return null; diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java index 3d691c7b22..c85a057060 100644 --- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java @@ -330,6 +330,25 @@ public abstract class AhatInstance implements Diffable<AhatInstance> { } /** + * Returns true if this is an instance of a (subclass of a) class with the + * given name. + * + * @param className the name of the class to check for + * @return true if this is an instance of a (subclass of a) class with the + * given name + */ + public boolean isInstanceOfClass(String className) { + AhatClassObj cls = getClassObj(); + while (cls != null) { + if (className.equals(cls.getName())) { + return true; + } + cls = cls.getSuperClassObj(); + } + return false; + } + + /** * Returns true if the given instance is an array instance. * * @return true if the given instance is an array instance diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Site.java b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java index 46a17296b7..4f0660fb50 100644 --- a/tools/ahat/src/main/com/android/ahat/heapdump/Site.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Predicate; /** * Used to collection information about objects allocated at a particular @@ -259,22 +261,37 @@ public class Site implements Diffable<Site> { * every heap should be collected. * @param className the name of the class the collected objects should * belong to. This may be null to indicate objects of - * every class should be collected. + * every class should be collected. Instances of subclasses + * of this class are not included. * @param objects out parameter. A collection of objects that all * collected objects should be added to. */ public void getObjects(String heapName, String className, Collection<AhatInstance> objects) { + Predicate<AhatInstance> predicate = x -> { + return (heapName == null || x.getHeap().getName().equals(heapName)) + && (className == null || x.getClassName().equals(className)); + }; + getObjects(predicate, x -> objects.add(x)); + } + + /** + * Collects the objects allocated under this site, filtered by the given + * predicate. + * Includes objects allocated in children sites. + * @param predicate limit instances to those satisfying this predicate + * @param consumer consumer of the objects + */ + public void getObjects(Predicate<AhatInstance> predicate, Consumer<AhatInstance> consumer) { for (AhatInstance inst : mObjects) { - if ((heapName == null || inst.getHeap().getName().equals(heapName)) - && (className == null || inst.getClassName().equals(className))) { - objects.add(inst); + if (predicate.test(inst)) { + consumer.accept(inst); } } // Recursively visit children. Recursion should be okay here because the // stack depth is limited by a reasonable amount (128 frames or so). for (Site child : mChildren) { - child.getObjects(heapName, className, objects); + child.getObjects(predicate, consumer); } } diff --git a/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java index 3aa52b5bb8..abc3cc7561 100644 --- a/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java +++ b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java @@ -29,6 +29,7 @@ import org.junit.runners.Suite; InstanceTest.class, NativeAllocationTest.class, ObjectHandlerTest.class, + ObjectsHandlerTest.class, OverviewHandlerTest.class, PerformanceTest.class, ProguardMapTest.class, diff --git a/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java b/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java new file mode 100644 index 0000000000..927e017ba9 --- /dev/null +++ b/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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.ahat.heapdump.AhatInstance; +import com.android.ahat.heapdump.AhatSnapshot; +import com.android.ahat.heapdump.Site; +import java.io.IOException; +import java.util.List; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ObjectsHandlerTest { + @Test + public void getObjects() throws IOException { + TestDump dump = TestDump.getTestDump(); + AhatSnapshot snapshot = dump.getAhatSnapshot(); + + Site root = snapshot.getRootSite(); + + // We expect a single instance of DumpedStuff + List<AhatInstance> dumped = ObjectsHandler.getObjects( + root, "DumpedStuff", /* subclass */ false, /* heapName */ null); + assertEquals(1, dumped.size()); + assertTrue(dumped.get(0).getClassName().equals("DumpedStuff")); + + // We expect no direct instances of SuperDumpedStuff + List<AhatInstance> direct = ObjectsHandler.getObjects( + root, "SuperDumpedStuff", /* subclass */ false, /* heapName */ null); + assertTrue(direct.isEmpty()); + + // We expect one subclass instance of SuperDumpedStuff + List<AhatInstance> subclass = ObjectsHandler.getObjects( + root, "SuperDumpedStuff", /* subclass */ true, /* heapName */ null); + assertEquals(1, subclass.size()); + assertTrue(subclass.get(0).getClassName().equals("DumpedStuff")); + assertEquals(dumped.get(0), subclass.get(0)); + } +} diff --git a/tools/ahat/src/test/com/android/ahat/QueryTest.java b/tools/ahat/src/test/com/android/ahat/QueryTest.java index 5bcf8eafc3..52cf963bd9 100644 --- a/tools/ahat/src/test/com/android/ahat/QueryTest.java +++ b/tools/ahat/src/test/com/android/ahat/QueryTest.java @@ -41,6 +41,7 @@ public class QueryTest { assertEquals("/object?answer=43&foo=bar", query.with("answer", "43").toString()); assertEquals("/object?answer=43&foo=bar", query.with("answer", 43).toString()); assertEquals("/object?answer=42&bar=finally&foo=bar", query.with("bar", "finally").toString()); + assertEquals("/object?answer=42", query.with("foo", null).toString()); } @Test @@ -55,6 +56,7 @@ public class QueryTest { assertEquals("/object?answer=43&foo=sludge", query.with("answer", "43").toString()); assertEquals("/object?answer=42&bar=finally&foo=sludge", query.with("bar", "finally").toString()); + assertEquals("/object?answer=42", query.with("foo", null).toString()); } @Test @@ -66,5 +68,6 @@ public class QueryTest { assertEquals(2, query.getInt("foo", 2)); assertEquals("/object?foo=sludge", query.with("foo", "sludge").toString()); assertEquals("/object?answer=43", query.with("answer", "43").toString()); + assertEquals("/object?", query.with("foo", null).toString()); } } |