• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 HARD_REFS_ID = "refs";
48   private static final String SOFT_REFS_ID = "srefs";
49 
50   private AhatSnapshot mSnapshot;
51 
ObjectHandler(AhatSnapshot snapshot)52   public ObjectHandler(AhatSnapshot snapshot) {
53     mSnapshot = snapshot;
54   }
55 
56   @Override
handle(Doc doc, Query query)57   public void handle(Doc doc, Query query) throws IOException {
58     long id = query.getLong("id", 0);
59     AhatInstance inst = mSnapshot.findInstance(id);
60     if (inst == null) {
61       doc.println(DocString.format("No object with id %08xl", id));
62       return;
63     }
64     AhatInstance base = inst.getBaseline();
65 
66     doc.title("Object %08x", inst.getId());
67     doc.big(Summarizer.summarize(inst));
68 
69     printAllocationSite(doc, query, inst);
70 
71     if (!inst.isUnreachable()) {
72       printGcRootPath(doc, query, inst);
73     }
74 
75     doc.section("Object Info");
76     AhatClassObj cls = inst.getClassObj();
77     doc.descriptions();
78     doc.description(DocString.text("Class"), Summarizer.summarize(cls));
79 
80     doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
81 
82     Collection<RootType> rootTypes = inst.getRootTypes();
83     if (rootTypes != null) {
84       DocString types = new DocString();
85       String comma = "";
86       for (RootType type : rootTypes) {
87         types.append(comma);
88         types.append(type.toString());
89         comma = ", ";
90       }
91       doc.description(DocString.text("Root Types"), types);
92     }
93 
94     doc.end();
95 
96     doc.section("Object Size");
97     SizeTable.table(doc, new Column(""), inst != base && !base.isPlaceHolder());
98     SizeTable.row(doc, DocString.text("Shallow"), inst.getSize(), base.getSize());
99     SizeTable.row(doc, DocString.text("Retained"),
100         inst.getTotalRetainedSize(), base.getTotalRetainedSize());
101     SizeTable.end(doc);
102 
103     printBitmap(doc, inst);
104     if (inst.isClassInstance()) {
105       printClassInstanceFields(doc, query, inst.asClassInstance());
106     } else if (inst.isArrayInstance()) {
107       printArrayElements(doc, query, inst.asArrayInstance());
108     } else if (inst.isClassObj()) {
109       printClassInfo(doc, query, inst.asClassObj());
110     }
111     printReferences(doc, query, inst);
112     printDominatedObjects(doc, query, inst);
113   }
114 
printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst)115   private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
116     doc.section("Fields");
117     AhatInstance base = inst.getBaseline();
118     printFields(doc, query, INSTANCE_FIELDS_ID, inst != base && !base.isPlaceHolder(),
119         inst.asClassInstance().getInstanceFields(),
120         base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
121   }
122 
printArrayElements(Doc doc, Query query, AhatArrayInstance array)123   private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
124     doc.section("Array Elements");
125     AhatInstance base = array.getBaseline();
126     boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
127     doc.table(
128         new Column("Index", Column.Align.RIGHT),
129         new Column("Value"),
130         new Column("Δ", Column.Align.LEFT, diff));
131 
132     List<Value> elements = array.getValues();
133     SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
134     int i = 0;
135     for (Value current : selector.selected()) {
136       DocString delta = new DocString();
137       if (diff) {
138         Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
139         if (!Objects.equals(current, previous)) {
140           delta.append("was ");
141           delta.append(Summarizer.summarize(previous));
142         }
143       }
144       doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
145       i++;
146     }
147     doc.end();
148     selector.render(doc);
149   }
150 
151   /**
152    * Helper function for printing static or instance fields.
153    * @param id - id to use for the field subset selector.
154    * @param diff - whether a diff should be shown for the fields.
155    * @param current - the fields to show.
156    * @param baseline - the baseline fields to diff against if diff is true,
157    *                   ignored otherwise.
158    */
printFields(Doc doc, Query query, String id, boolean diff, Iterable<FieldValue> current, Iterable<FieldValue> baseline)159   private static void printFields(Doc doc, Query query, String id, boolean diff,
160       Iterable<FieldValue> current, Iterable<FieldValue> baseline) {
161 
162     if (!diff) {
163       // To avoid having to special case when diff is disabled, always diff
164       // the fields, but diff against an empty list.
165       baseline = Collections.emptyList();
166     }
167 
168     List<DiffedFieldValue> fields = DiffFields.diff(current, baseline);
169     SubsetSelector<DiffedFieldValue> selector = new SubsetSelector(query, id, fields);
170 
171     doc.table(
172         new Column("Type"),
173         new Column("Name"),
174         new Column("Value"),
175         new Column("Δ", Column.Align.LEFT, diff));
176 
177     for (DiffedFieldValue field : selector.selected()) {
178       Value previous = Value.getBaseline(field.baseline);
179       DocString was = DocString.text("was ");
180       was.append(Summarizer.summarize(previous));
181       switch (field.status) {
182         case ADDED:
183           doc.row(DocString.text(field.type.name),
184                   DocString.text(field.name),
185                   Summarizer.summarize(field.current),
186                   DocString.added("new"));
187           break;
188 
189         case MATCHED:
190           doc.row(DocString.text(field.type.name),
191                   DocString.text(field.name),
192                   Summarizer.summarize(field.current),
193                   Objects.equals(field.current, previous) ? new DocString() : was);
194           break;
195 
196         case DELETED:
197           doc.row(DocString.text(field.type.name),
198                   DocString.text(field.name),
199                   DocString.removed("del"),
200                   was);
201           break;
202       }
203     }
204     doc.end();
205     selector.render(doc);
206   }
207 
printClassInfo(Doc doc, Query query, AhatClassObj clsobj)208   private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
209     doc.section("Class Info");
210     doc.descriptions();
211     doc.description(DocString.text("Super Class"),
212         Summarizer.summarize(clsobj.getSuperClassObj()));
213     doc.description(DocString.text("Class Loader"),
214         Summarizer.summarize(clsobj.getClassLoader()));
215     doc.end();
216 
217     doc.section("Static Fields");
218     AhatInstance base = clsobj.getBaseline();
219     printFields(doc, query, STATIC_FIELDS_ID, clsobj != base && !base.isPlaceHolder(),
220         clsobj.getStaticFieldValues(),
221         base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
222   }
223 
printReferences(Doc doc, Query query, AhatInstance inst)224   private static void printReferences(Doc doc, Query query, AhatInstance inst) {
225     doc.section("Objects with References to this Object");
226     if (inst.getHardReverseReferences().isEmpty()) {
227       doc.println(DocString.text("(none)"));
228     } else {
229       doc.table(new Column("Object"));
230       List<AhatInstance> references = inst.getHardReverseReferences();
231       SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
232       for (AhatInstance ref : selector.selected()) {
233         doc.row(Summarizer.summarize(ref));
234       }
235       doc.end();
236       selector.render(doc);
237     }
238 
239     if (!inst.getSoftReverseReferences().isEmpty()) {
240       doc.section("Objects with Soft References to this Object");
241       doc.table(new Column("Object"));
242       List<AhatInstance> references = inst.getSoftReverseReferences();
243       SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
244       for (AhatInstance ref : selector.selected()) {
245         doc.row(Summarizer.summarize(ref));
246       }
247       doc.end();
248       selector.render(doc);
249     }
250   }
251 
printAllocationSite(Doc doc, Query query, AhatInstance inst)252   private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
253     doc.section("Allocation Site");
254     Site site = inst.getSite();
255     SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
256   }
257 
258   // Draw the bitmap corresponding to this instance if there is one.
printBitmap(Doc doc, AhatInstance inst)259   private static void printBitmap(Doc doc, AhatInstance inst) {
260     AhatInstance bitmap = inst.getAssociatedBitmapInstance();
261     if (bitmap != null) {
262       doc.section("Bitmap Image");
263       doc.println(DocString.image(
264             DocString.formattedUri("bitmap?id=0x%x", bitmap.getId()), "bitmap image"));
265     }
266   }
267 
printGcRootPath(Doc doc, Query query, AhatInstance inst)268   private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
269     doc.section("Sample Path from GC Root");
270     List<PathElement> path = inst.getPathFromGcRoot();
271 
272     // Add a dummy PathElement as a marker for the root.
273     final PathElement root = new PathElement(null, null);
274     path.add(0, root);
275 
276     HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
277       public String getHeapsDescription() {
278         return "Bytes Retained by Heap (Dominators Only)";
279       }
280 
281       public long getSize(PathElement element, AhatHeap heap) {
282         if (element == root) {
283           return heap.getSize().getSize();
284         }
285         if (element.isDominator) {
286           return element.instance.getRetainedSize(heap).getSize();
287         }
288         return 0;
289       }
290 
291       public List<HeapTable.ValueConfig<PathElement>> getValueConfigs() {
292         HeapTable.ValueConfig<PathElement> value = new HeapTable.ValueConfig<PathElement>() {
293           public String getDescription() {
294             return "Path Element";
295           }
296 
297           public DocString render(PathElement element) {
298             if (element == root) {
299               return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
300             } else {
301               DocString label = DocString.text("→ ");
302               label.append(Summarizer.summarize(element.instance));
303               label.append(element.field);
304               return label;
305             }
306           }
307         };
308         return Collections.singletonList(value);
309       }
310     };
311     HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
312   }
313 
printDominatedObjects(Doc doc, Query query, AhatInstance inst)314   public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
315     doc.section("Immediately Dominated Objects");
316     List<AhatInstance> instances = inst.getDominated();
317     if (instances != null) {
318       DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
319     } else {
320       doc.println(DocString.text("(none)"));
321     }
322   }
323 }
324 
325