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.heapdump; 18 19 import com.android.ahat.proguard.ProguardMap; 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 27 /** 28 * Used to collection information about objects allocated at a particular 29 * allocation site. 30 */ 31 public class Site implements Diffable<Site> { 32 // The site that this site was directly called from. 33 // mParent is null for the root site. 34 private Site mParent; 35 36 private String mMethodName; 37 private String mSignature; 38 private String mFilename; 39 private int mLineNumber; 40 41 // A unique id to identify this site with. The id is chosen based on a 42 // depth first traversal of the complete site tree, which gives it the 43 // following desired properties: 44 // * The id can easily be represented in a URL. 45 // * The id is determined by the hprof file, so that the same id can be used 46 // across different instances for viewing the same hprof file. 47 // * A binary search can be used to find a site by id from the root site in 48 // log time. 49 // 50 // The id is set by prepareForUse after the complete site tree is constructed. 51 private long mId = -1; 52 53 // The total size of objects allocated in this site (including child sites), 54 // organized by heap index. Computed as part of prepareForUse. 55 private Size[] mSizesByHeap; 56 57 // List of child sites. 58 private List<Site> mChildren; 59 60 // List of objects allocated at this site (not including child sites). 61 private List<AhatInstance> mObjects; 62 63 private List<ObjectsInfo> mObjectsInfos; 64 private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap; 65 66 private Site mBaseline; 67 68 /** 69 * Summary information about instances allocated at a particular allocation 70 * site that are instances of a particular class and allocated on a 71 * particular heap. 72 */ 73 public static class ObjectsInfo implements Diffable<ObjectsInfo> { 74 /** 75 * The heap that the summarized objects belong to. 76 */ 77 public AhatHeap heap; 78 79 /** 80 * The class of the summarized objects. 81 */ 82 public AhatClassObj classObj; // May be null. Not sure why. 83 84 /** 85 * The number of instances included in the summary. 86 */ 87 public long numInstances; 88 89 /** 90 * The sum of the shallow size of each instance included in the summary. 91 */ 92 public Size numBytes; 93 94 private ObjectsInfo baseline; 95 96 /** 97 * Constructs a new, empty objects info for the given heap and class 98 * combination. 99 */ ObjectsInfo(AhatHeap heap, AhatClassObj classObj)100 ObjectsInfo(AhatHeap heap, AhatClassObj classObj) { 101 this.heap = heap; 102 this.classObj = classObj; 103 this.numInstances = 0; 104 this.numBytes = Size.ZERO; 105 this.baseline = this; 106 } 107 108 /** 109 * Returns the name of the class this ObjectsInfo is associated with. 110 * 111 * @return the name of this object info's class 112 */ getClassName()113 public String getClassName() { 114 return classObj == null ? "???" : classObj.getName(); 115 } 116 setBaseline(ObjectsInfo baseline)117 void setBaseline(ObjectsInfo baseline) { 118 this.baseline = baseline; 119 } 120 getBaseline()121 @Override public ObjectsInfo getBaseline() { 122 return baseline; 123 } 124 isPlaceHolder()125 @Override public boolean isPlaceHolder() { 126 return false; 127 } 128 } 129 130 /** 131 * Construct a root site. 132 */ Site(String name)133 Site(String name) { 134 this(null, name, "", "", 0); 135 } 136 Site(Site parent, String method, String signature, String file, int line)137 private Site(Site parent, String method, String signature, String file, int line) { 138 mParent = parent; 139 mMethodName = method; 140 mSignature = signature; 141 mFilename = file; 142 mLineNumber = line; 143 mChildren = new ArrayList<Site>(); 144 mObjects = new ArrayList<AhatInstance>(); 145 mObjectsInfos = new ArrayList<ObjectsInfo>(); 146 mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>(); 147 mBaseline = this; 148 } 149 150 /** 151 * Gets a child site of this site. 152 * @param frames the list of frames in the stack trace, starting with the 153 * inner-most frame. May be null, in which case this site is 154 * returned. 155 * @return the child site 156 */ getSite(ProguardMap.Frame[] frames)157 Site getSite(ProguardMap.Frame[] frames) { 158 return frames == null ? this : getSite(this, frames); 159 } 160 getSite(Site site, ProguardMap.Frame[] frames)161 private static Site getSite(Site site, ProguardMap.Frame[] frames) { 162 for (int s = frames.length - 1; s >= 0; --s) { 163 ProguardMap.Frame frame = frames[s]; 164 Site child = null; 165 for (int i = 0; i < site.mChildren.size(); i++) { 166 Site curr = site.mChildren.get(i); 167 if (curr.mLineNumber == frame.line 168 && curr.mMethodName.equals(frame.method) 169 && curr.mSignature.equals(frame.signature) 170 && curr.mFilename.equals(frame.filename)) { 171 child = curr; 172 break; 173 } 174 } 175 if (child == null) { 176 child = new Site(site, frame.method, frame.signature, 177 frame.filename, frame.line); 178 site.mChildren.add(child); 179 } 180 site = child; 181 } 182 return site; 183 } 184 185 /** 186 * Add an instance allocated at this site. 187 */ addInstance(AhatInstance inst)188 void addInstance(AhatInstance inst) { 189 mObjects.add(inst); 190 } 191 192 /** 193 * Prepare this and all child sites for use. 194 * Recomputes site ids, sizes, ObjectInfos for this and all child sites. 195 * This should be called after the sites tree has been formed and after 196 * dominators computation has been performed to ensure only reachable 197 * objects are included in the ObjectsInfos. 198 * 199 * @param id - The smallest id that is allowed to be used for this site or 200 * any of its children. 201 * @param numHeaps - The number of heaps in the heap dump. 202 * @return An id larger than the largest id used for this site or any of its 203 * children. 204 */ prepareForUse(long id, int numHeaps)205 long prepareForUse(long id, int numHeaps) { 206 mId = id++; 207 208 // Count up the total sizes by heap. 209 mSizesByHeap = new Size[numHeaps]; 210 for (int i = 0; i < numHeaps; ++i) { 211 mSizesByHeap[i] = Size.ZERO; 212 } 213 214 // Add all reachable objects allocated at this site. 215 for (AhatInstance inst : mObjects) { 216 if (inst.isStronglyReachable()) { 217 AhatHeap heap = inst.getHeap(); 218 Size size = inst.getSize(); 219 ObjectsInfo info = getObjectsInfo(heap, inst.getClassObj()); 220 info.numInstances++; 221 info.numBytes = info.numBytes.plus(size); 222 mSizesByHeap[heap.getIndex()] = mSizesByHeap[heap.getIndex()].plus(size); 223 } 224 } 225 226 // Add objects allocated in child sites. 227 for (Site child : mChildren) { 228 id = child.prepareForUse(id, numHeaps); 229 for (ObjectsInfo childInfo : child.mObjectsInfos) { 230 ObjectsInfo info = getObjectsInfo(childInfo.heap, childInfo.classObj); 231 info.numInstances += childInfo.numInstances; 232 info.numBytes = info.numBytes.plus(childInfo.numBytes); 233 } 234 for (int i = 0; i < numHeaps; ++i) { 235 mSizesByHeap[i] = mSizesByHeap[i].plus(child.mSizesByHeap[i]); 236 } 237 } 238 return id; 239 } 240 241 /** 242 * Returns the size of all objects on the given heap allocated at this site. 243 * Includes objects belonging to <code>heap</code> allocated at this and 244 * child sites. 245 * 246 * @param heap the heap to query the size for 247 * @return the total shallow size of objects in this site 248 */ getSize(AhatHeap heap)249 public Size getSize(AhatHeap heap) { 250 return mSizesByHeap[heap.getIndex()]; 251 } 252 253 /** 254 * Collects the objects allocated under this site, optionally filtered by 255 * heap name or class name. Includes objects allocated in children sites. 256 * @param heapName the name of the heap the collected objects should 257 * belong to. This may be null to indicate objects of 258 * every heap should be collected. 259 * @param className the name of the class the collected objects should 260 * belong to. This may be null to indicate objects of 261 * every class should be collected. 262 * @param objects out parameter. A collection of objects that all 263 * collected objects should be added to. 264 */ getObjects(String heapName, String className, Collection<AhatInstance> objects)265 public void getObjects(String heapName, String className, Collection<AhatInstance> objects) { 266 for (AhatInstance inst : mObjects) { 267 if ((heapName == null || inst.getHeap().getName().equals(heapName)) 268 && (className == null || inst.getClassName().equals(className))) { 269 objects.add(inst); 270 } 271 } 272 273 // Recursively visit children. Recursion should be okay here because the 274 // stack depth is limited by a reasonable amount (128 frames or so). 275 for (Site child : mChildren) { 276 child.getObjects(heapName, className, objects); 277 } 278 } 279 280 /** 281 * Returns the ObjectsInfo at this site for the given heap and class 282 * objects. Creates a new empty ObjectsInfo if none existed before. 283 */ getObjectsInfo(AhatHeap heap, AhatClassObj classObj)284 ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) { 285 Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap); 286 if (classToObjectsInfo == null) { 287 classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>(); 288 mObjectsInfoMap.put(heap, classToObjectsInfo); 289 } 290 291 ObjectsInfo info = classToObjectsInfo.get(classObj); 292 if (info == null) { 293 info = new ObjectsInfo(heap, classObj); 294 mObjectsInfos.add(info); 295 classToObjectsInfo.put(classObj, info); 296 } 297 return info; 298 } 299 300 /** 301 * Return a summary breakdown of the objects allocated at this site. 302 * Objects are grouped by class and heap and summarized into a single 303 * {@link ObjectsInfo}. This method returns all the groups for this 304 * allocation site. 305 * 306 * @return all ObjectInfo summaries for instances allocated at this site 307 */ getObjectsInfos()308 public List<ObjectsInfo> getObjectsInfos() { 309 return mObjectsInfos; 310 } 311 312 /** 313 * Returns the combined size of the site for all heaps. 314 * Includes all objects allocated at this and child sites. 315 * 316 * @return total shallow size of objects in this site 317 */ getTotalSize()318 public Size getTotalSize() { 319 Size total = Size.ZERO; 320 for (Size size : mSizesByHeap) { 321 total = total.plus(size); 322 } 323 return total; 324 } 325 326 /** 327 * Returns the site this site was called from. 328 * Returns null for the root site. 329 * 330 * @return the site this site was called from 331 */ getParent()332 public Site getParent() { 333 return mParent; 334 } 335 336 /** 337 * Returns the name of the method this allocation site belongs to. 338 * For example, "equals". 339 * 340 * @return the method name of the allocation site 341 */ getMethodName()342 public String getMethodName() { 343 return mMethodName; 344 } 345 346 /** 347 * Returns the signature of the method this allocation site belongs to. 348 * For example, "(Ljava/lang/Object;)Z". 349 * 350 * @return the signature of method the allocation site belongs to 351 */ getSignature()352 public String getSignature() { 353 return mSignature; 354 } 355 356 /** 357 * Returns the name of the Java file where this allocation site is found. 358 * 359 * @return the file the allocation site belongs to 360 */ getFilename()361 public String getFilename() { 362 return mFilename; 363 } 364 365 /** 366 * Returns the line number of the code in the source file that the 367 * allocation site refers to. 368 * 369 * @return the allocation site line number 370 */ getLineNumber()371 public int getLineNumber() { 372 return mLineNumber; 373 } 374 375 /** 376 * Returns the unique id of this site. 377 * This is an arbitrary unique id computed after processing the heap dump. 378 * 379 * @return the site id 380 */ getId()381 public long getId() { 382 return mId; 383 } 384 385 /** 386 * Returns the child site with the given id. 387 * Returns null if no such site was found. 388 * 389 * @param id the id of the child site to find 390 * @return the found child site 391 */ findSite(long id)392 public Site findSite(long id) { 393 if (id == mId) { 394 return this; 395 } 396 397 // Binary search over the children to find the right child to search in. 398 int start = 0; 399 int end = mChildren.size(); 400 while (start < end) { 401 int mid = start + ((end - start) / 2); 402 Site midSite = mChildren.get(mid); 403 if (id < midSite.mId) { 404 end = mid; 405 } else if (mid + 1 == end) { 406 // This is the last child we could possibly find the desired site in, 407 // so search in this child. 408 return midSite.findSite(id); 409 } else if (id < mChildren.get(mid + 1).mId) { 410 // The desired site has an id between this child's id and the next 411 // child's id, so search in this child. 412 return midSite.findSite(id); 413 } else { 414 start = mid + 1; 415 } 416 } 417 return null; 418 } 419 420 /** 421 * Returns an unmodifiable list of this site's immediate children. 422 * 423 * @return this site's child sites 424 */ getChildren()425 public List<Site> getChildren() { 426 return Collections.unmodifiableList(mChildren); 427 } 428 setBaseline(Site baseline)429 void setBaseline(Site baseline) { 430 mBaseline = baseline; 431 } 432 getBaseline()433 @Override public Site getBaseline() { 434 return mBaseline; 435 } 436 isPlaceHolder()437 @Override public boolean isPlaceHolder() { 438 return false; 439 } 440 } 441