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