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