| /* |
| * 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.RootType; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static com.android.ahat.InstanceUtils.PathElement; |
| |
| 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) { |
| mSnapshot = 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.format("No object with id %08xl", id)); |
| return; |
| } |
| |
| doc.title("Object %08x", inst.getUniqueId()); |
| doc.big(Value.render(mSnapshot, inst)); |
| |
| printAllocationSite(doc, query, inst); |
| printGcRootPath(doc, query, inst); |
| |
| doc.section("Object Info"); |
| ClassObj cls = inst.getClassObj(); |
| doc.descriptions(); |
| doc.description(DocString.text("Class"), Value.render(mSnapshot, cls)); |
| doc.description(DocString.text("Size"), DocString.format("%d", inst.getSize())); |
| doc.description( |
| DocString.text("Retained Size"), |
| DocString.format("%d", inst.getTotalRetainedSize())); |
| doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName())); |
| |
| Collection<RootType> rootTypes = mSnapshot.getRootTypes(inst); |
| if (rootTypes != null) { |
| DocString types = new DocString(); |
| String comma = ""; |
| for (RootType type : rootTypes) { |
| types.append(comma); |
| types.append(type.getName()); |
| comma = ", "; |
| } |
| doc.description(DocString.text("Root Types"), types); |
| } |
| |
| doc.end(); |
| |
| printBitmap(doc, inst); |
| if (inst instanceof ClassInstance) { |
| printClassInstanceFields(doc, query, mSnapshot, (ClassInstance)inst); |
| } else if (inst instanceof ArrayInstance) { |
| printArrayElements(doc, query, mSnapshot, (ArrayInstance)inst); |
| } else if (inst instanceof ClassObj) { |
| printClassInfo(doc, query, mSnapshot, (ClassObj)inst); |
| } |
| printReferences(doc, query, mSnapshot, inst); |
| printDominatedObjects(doc, query, inst); |
| } |
| |
| private static void printClassInstanceFields( |
| Doc doc, Query query, AhatSnapshot snapshot, ClassInstance inst) { |
| doc.section("Fields"); |
| doc.table(new Column("Type"), new Column("Name"), new Column("Value")); |
| 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(snapshot, field.getValue())); |
| } |
| doc.end(); |
| selector.render(doc); |
| } |
| |
| private static void printArrayElements( |
| Doc doc, Query query, AhatSnapshot snapshot, ArrayInstance array) { |
| doc.section("Array Elements"); |
| doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value")); |
| 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(snapshot, elem)); |
| i++; |
| } |
| doc.end(); |
| selector.render(doc); |
| } |
| |
| private static void printClassInfo( |
| Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) { |
| doc.section("Class Info"); |
| doc.descriptions(); |
| doc.description(DocString.text("Super Class"), |
| Value.render(snapshot, clsobj.getSuperClassObj())); |
| doc.description(DocString.text("Class Loader"), |
| Value.render(snapshot, clsobj.getClassLoader())); |
| doc.end(); |
| |
| doc.section("Static Fields"); |
| doc.table(new Column("Type"), new Column("Name"), new Column("Value")); |
| 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(snapshot, field.getValue())); |
| } |
| doc.end(); |
| selector.render(doc); |
| } |
| |
| private static void printReferences( |
| Doc doc, Query query, AhatSnapshot snapshot, Instance inst) { |
| doc.section("Objects with References to this Object"); |
| if (inst.getHardReverseReferences().isEmpty()) { |
| doc.println(DocString.text("(none)")); |
| } else { |
| doc.table(new Column("Object")); |
| List<Instance> references = inst.getHardReverseReferences(); |
| SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references); |
| for (Instance ref : selector.selected()) { |
| doc.row(Value.render(snapshot, ref)); |
| } |
| doc.end(); |
| selector.render(doc); |
| } |
| |
| if (inst.getSoftReverseReferences() != null) { |
| doc.section("Objects with Soft References to this Object"); |
| doc.table(new Column("Object")); |
| List<Instance> references = inst.getSoftReverseReferences(); |
| SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references); |
| for (Instance ref : selector.selected()) { |
| doc.row(Value.render(snapshot, ref)); |
| } |
| doc.end(); |
| selector.render(doc); |
| } |
| } |
| |
| private void printAllocationSite(Doc doc, Query query, Instance inst) { |
| doc.section("Allocation Site"); |
| Site site = mSnapshot.getSiteForInstance(inst); |
| SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, 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.formattedUri("bitmap?id=%d", bitmap.getId()), "bitmap image")); |
| } |
| } |
| |
| private void printGcRootPath(Doc doc, Query query, Instance inst) { |
| doc.section("Sample Path from GC Root"); |
| List<PathElement> path = InstanceUtils.getPathFromGcRoot(inst); |
| |
| // Add 'null' as a marker for the root. |
| path.add(0, null); |
| |
| HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() { |
| public String getHeapsDescription() { |
| return "Bytes Retained by Heap (Dominators Only)"; |
| } |
| |
| public long getSize(PathElement element, Heap heap) { |
| if (element == null) { |
| return mSnapshot.getHeapSize(heap); |
| } |
| if (element.isDominator) { |
| int index = mSnapshot.getHeapIndex(heap); |
| return element.instance.getRetainedSize(index); |
| } |
| return 0; |
| } |
| |
| public List<HeapTable.ValueConfig<PathElement>> getValueConfigs() { |
| HeapTable.ValueConfig<PathElement> value = new HeapTable.ValueConfig<PathElement>() { |
| public String getDescription() { |
| return "Path Element"; |
| } |
| |
| public DocString render(PathElement element) { |
| if (element == null) { |
| return DocString.link(DocString.uri("rooted"), DocString.text("ROOT")); |
| } else { |
| DocString label = DocString.text(" → "); |
| label.append(Value.render(mSnapshot, element.instance)); |
| label.append(element.field); |
| return label; |
| } |
| } |
| }; |
| return Collections.singletonList(value); |
| } |
| }; |
| 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, query, DOMINATED_OBJECTS_ID, instances); |
| } else { |
| doc.println(DocString.text("(none)")); |
| } |
| } |
| } |
| |