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