1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat;
18 
19 import com.android.ahat.heapdump.AhatArrayInstance;
20 import com.android.ahat.heapdump.AhatClassInstance;
21 import com.android.ahat.heapdump.AhatClassObj;
22 import com.android.ahat.heapdump.AhatHeap;
23 import com.android.ahat.heapdump.AhatInstance;
24 import com.android.ahat.heapdump.AhatSnapshot;
25 import com.android.ahat.heapdump.DiffFields;
26 import com.android.ahat.heapdump.DiffedFieldValue;
27 import com.android.ahat.heapdump.FieldValue;
28 import com.android.ahat.heapdump.PathElement;
29 import com.android.ahat.heapdump.RootType;
30 import com.android.ahat.heapdump.Site;
31 import com.android.ahat.heapdump.Value;
32 import java.io.IOException;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Objects;
37 
38 
39 class ObjectHandler implements AhatHandler {
40 
41   private static final String ARRAY_ELEMENTS_ID = "elements";
42   private static final String DOMINATOR_PATH_ID = "dompath";
43   private static final String ALLOCATION_SITE_ID = "frames";
44   private static final String DOMINATED_OBJECTS_ID = "dominated";
45   private static final String INSTANCE_FIELDS_ID = "ifields";
46   private static final String STATIC_FIELDS_ID = "sfields";
47   private static final String REFS_ID = "refs";
48 
49   private AhatSnapshot mSnapshot;
50 
ObjectHandler(AhatSnapshot snapshot)51   public ObjectHandler(AhatSnapshot snapshot) {
52     mSnapshot = snapshot;
53   }
54 
55   @Override
handle(Doc doc, Query query)56   public void handle(Doc doc, Query query) throws IOException {
57     long id = query.getLong("id", 0);
58     AhatInstance inst = mSnapshot.findInstance(id);
59     if (inst == null) {
60       doc.println(DocString.format("No object with id %08xl", id));
61       return;
62     }
63     AhatInstance base = inst.getBaseline();
64 
65     doc.title("Object %08x", inst.getId());
66     doc.big(Summarizer.summarize(inst));
67 
68     printAllocationSite(doc, query, inst);
69 
70     if (!inst.isUnreachable()) {
71       printGcRootPath(doc, query, inst);
72     }
73 
74     doc.section("Object Info");
75     AhatClassObj cls = inst.getClassObj();
76     doc.descriptions();
77     doc.description(DocString.text("Class"), Summarizer.summarize(cls));
78 
79     doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
80 
81     Collection<RootType> rootTypes = inst.getRootTypes();
82     if (rootTypes != null) {
83       DocString types = new DocString();
84       String comma = "";
85       for (RootType type : rootTypes) {
86         types.append(comma);
87         types.append(type.toString());
88         comma = ", ";
89       }
90       doc.description(DocString.text("Root Types"), types);
91     }
92 
93     doc.end();
94 
95     doc.section("Object Size");
96     SizeTable.table(doc, new Column(""), inst != base && !base.isPlaceHolder());
97     SizeTable.row(doc, DocString.text("Shallow"), inst.getSize(), base.getSize());
98     SizeTable.row(doc, DocString.text("Retained"),
99         inst.getTotalRetainedSize(), base.getTotalRetainedSize());
100     SizeTable.end(doc);
101 
102     printBitmap(doc, inst);
103     if (inst.isClassInstance()) {
104       printClassInstanceFields(doc, query, inst.asClassInstance());
105     } else if (inst.isArrayInstance()) {
106       printArrayElements(doc, query, inst.asArrayInstance());
107     } else if (inst.isClassObj()) {
108       printClassInfo(doc, query, inst.asClassObj());
109     }
110     printReferences(doc, query, inst);
111     printDominatedObjects(doc, query, inst);
112   }
113 
printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst)114   private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
115     doc.section("Fields");
116     AhatInstance base = inst.getBaseline();
117     printFields(doc, query, INSTANCE_FIELDS_ID, inst != base && !base.isPlaceHolder(),
118         inst.asClassInstance().getInstanceFields(),
119         base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
120   }
121 
printArrayElements(Doc doc, Query query, AhatArrayInstance array)122   private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
123     doc.section("Array Elements");
124     AhatInstance base = array.getBaseline();
125     boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
126     doc.table(
127         new Column("Index", Column.Align.RIGHT),
128         new Column("Value"),
129         new Column("Δ", Column.Align.LEFT, diff));
130 
131     List<Value> elements = array.getValues();
132     SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
133     int i = 0;
134     for (Value current : selector.selected()) {
135       DocString delta = new DocString();
136       if (diff) {
137         Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
138         if (!Objects.equals(current, previous)) {
139           delta.append("was ");
140           delta.append(Summarizer.summarize(previous));
141         }
142       }
143       doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
144       i++;
145     }
146     doc.end();
147     selector.render(doc);
148   }
149 
150   /**
151    * Helper function for printing static or instance fields.
152    * @param id - id to use for the field subset selector.
153    * @param diff - whether a diff should be shown for the fields.
154    * @param current - the fields to show.
155    * @param baseline - the baseline fields to diff against if diff is true,
156    *                   ignored otherwise.
157    */
printFields(Doc doc, Query query, String id, boolean diff, Iterable<FieldValue> current, Iterable<FieldValue> baseline)158   private static void printFields(Doc doc, Query query, String id, boolean diff,
159       Iterable<FieldValue> current, Iterable<FieldValue> baseline) {
160 
161     if (!diff) {
162       // To avoid having to special case when diff is disabled, always diff
163       // the fields, but diff against an empty list.
164       baseline = Collections.emptyList();
165     }
166 
167     List<DiffedFieldValue> fields = DiffFields.diff(current, baseline);
168     SubsetSelector<DiffedFieldValue> selector = new SubsetSelector(query, id, fields);
169 
170     doc.table(
171         new Column("Type"),
172         new Column("Name"),
173         new Column("Value"),
174         new Column("Δ", Column.Align.LEFT, diff));
175 
176     for (DiffedFieldValue field : selector.selected()) {
177       Value previous = Value.getBaseline(field.baseline);
178       DocString was = DocString.text("was ");
179       was.append(Summarizer.summarize(previous));
180       switch (field.status) {
181         case ADDED:
182           doc.row(DocString.text(field.type.name),
183                   DocString.text(field.name),
184                   Summarizer.summarize(field.current),
185                   DocString.added("new"));
186           break;
187 
188         case MATCHED:
189           doc.row(DocString.text(field.type.name),
190                   DocString.text(field.name),
191                   Summarizer.summarize(field.current),
192                   Objects.equals(field.current, previous) ? new DocString() : was);
193           break;
194 
195         case DELETED:
196           doc.row(DocString.text(field.type.name),
197                   DocString.text(field.name),
198                   DocString.removed("del"),
199                   was);
200           break;
201       }
202     }
203     doc.end();
204     selector.render(doc);
205   }
206 
printClassInfo(Doc doc, Query query, AhatClassObj clsobj)207   private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
208     doc.section("Class Info");
209     doc.descriptions();
210     doc.description(DocString.text("Super Class"),
211         Summarizer.summarize(clsobj.getSuperClassObj()));
212     doc.description(DocString.text("Class Loader"),
213         Summarizer.summarize(clsobj.getClassLoader()));
214     doc.end();
215 
216     doc.section("Static Fields");
217     AhatInstance base = clsobj.getBaseline();
218     printFields(doc, query, STATIC_FIELDS_ID, clsobj != base && !base.isPlaceHolder(),
219         clsobj.getStaticFieldValues(),
220         base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
221   }
222 
printReferences(Doc doc, Query query, AhatInstance inst)223   private static void printReferences(Doc doc, Query query, AhatInstance inst) {
224     doc.section("Objects with References to this Object");
225     if (inst.getReverseReferences().isEmpty()) {
226       doc.println(DocString.text("(none)"));
227     } else {
228       doc.table(new Column("Object"));
229       List<AhatInstance> references = inst.getReverseReferences();
230       SubsetSelector<AhatInstance> selector = new SubsetSelector(query, REFS_ID, references);
231       for (AhatInstance ref : selector.selected()) {
232         doc.row(Summarizer.summarize(ref));
233       }
234       doc.end();
235       selector.render(doc);
236     }
237   }
238 
printAllocationSite(Doc doc, Query query, AhatInstance inst)239   private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
240     doc.section("Allocation Site");
241     Site site = inst.getSite();
242     SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
243   }
244 
245   // Draw the bitmap corresponding to this instance if there is one.
printBitmap(Doc doc, AhatInstance inst)246   private static void printBitmap(Doc doc, AhatInstance inst) {
247     AhatInstance bitmap = inst.getAssociatedBitmapInstance();
248     if (bitmap != null) {
249       doc.section("Bitmap Image");
250       doc.println(DocString.image(
251             DocString.formattedUri("bitmap?id=0x%x", bitmap.getId()), "bitmap image"));
252     }
253   }
254 
printGcRootPath(Doc doc, Query query, AhatInstance inst)255   private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
256     doc.section("Sample Path from GC Root");
257     List<PathElement> path = inst.getPathFromGcRoot();
258 
259     // Add a fake PathElement as a marker for the root.
260     final PathElement root = new PathElement(null, null);
261     path.add(0, root);
262 
263     HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
264       public String getHeapsDescription() {
265         return "Bytes Retained by Heap (Dominators Only)";
266       }
267 
268       public long getSize(PathElement element, AhatHeap heap) {
269         if (element == root) {
270           return heap.getSize().getSize();
271         }
272         if (element.isDominator) {
273           return element.instance.getRetainedSize(heap).getSize();
274         }
275         return 0;
276       }
277 
278       public List<HeapTable.ValueConfig<PathElement>> getValueConfigs() {
279         HeapTable.ValueConfig<PathElement> value = new HeapTable.ValueConfig<PathElement>() {
280           public String getDescription() {
281             return "Path Element";
282           }
283 
284           public DocString render(PathElement element) {
285             if (element == root) {
286               return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
287             } else {
288               DocString label = DocString.text("→ ");
289               label.append(Summarizer.summarize(element.instance));
290               label.append(element.field);
291               return label;
292             }
293           }
294         };
295         return Collections.singletonList(value);
296       }
297     };
298     HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
299   }
300 
printDominatedObjects(Doc doc, Query query, AhatInstance inst)301   public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
302     doc.section("Immediately Dominated Objects");
303     List<AhatInstance> instances = inst.getDominated();
304     if (instances != null) {
305       DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
306     } else {
307       doc.println(DocString.text("(none)"));
308     }
309   }
310 }
311