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