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