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