1 /*
2  * Copyright (C) 2016 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.tools.perflib.captures.DataBuffer;
20 import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
21 import com.android.tools.perflib.heap.ArrayInstance;
22 import com.android.tools.perflib.heap.ClassInstance;
23 import com.android.tools.perflib.heap.ClassObj;
24 import com.android.tools.perflib.heap.Heap;
25 import com.android.tools.perflib.heap.Instance;
26 import com.android.tools.perflib.heap.ProguardMap;
27 import com.android.tools.perflib.heap.RootObj;
28 import com.android.tools.perflib.heap.Snapshot;
29 import com.android.tools.perflib.heap.StackFrame;
30 import com.android.tools.perflib.heap.StackTrace;
31 import gnu.trove.TObjectProcedure;
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Comparator;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 
41 public class AhatSnapshot implements Diffable<AhatSnapshot> {
42   private final Site mRootSite = new Site("ROOT");
43 
44   // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
45   private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>();
46 
47   // List of all ahat instances stored in increasing order by id.
48   private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
49 
50   // Map from class name to class object.
51   private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>();
52 
53   private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
54 
55   private AhatSnapshot mBaseline = this;
56 
57   /**
58    * Create an AhatSnapshot from an hprof file.
59    */
fromHprof(File hprof, ProguardMap map)60   public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
61     return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map);
62   }
63 
64   /**
65    * Create an AhatSnapshot from an in-memory data buffer.
66    */
fromDataBuffer(DataBuffer buffer, ProguardMap map)67   public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException {
68     AhatSnapshot snapshot = new AhatSnapshot(buffer, map);
69 
70     // Request a GC now to clean up memory used by perflib. This helps to
71     // avoid a noticable pause when visiting the first interesting page in
72     // ahat.
73     System.gc();
74 
75     return snapshot;
76   }
77 
78   /**
79    * Constructs an AhatSnapshot for the given hprof binary data.
80    */
AhatSnapshot(DataBuffer buffer, ProguardMap map)81   private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
82     Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
83     snapshot.computeDominators();
84 
85     // Properly label the class of class objects in the perflib snapshot, and
86     // count the total number of instances.
87     final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
88     if (javaLangClass != null) {
89       for (Heap heap : snapshot.getHeaps()) {
90         Collection<ClassObj> classes = heap.getClasses();
91         for (ClassObj clsObj : classes) {
92           if (clsObj.getClassObj() == null) {
93             clsObj.setClassId(javaLangClass.getId());
94           }
95         }
96       }
97     }
98 
99     // Create mappings from id to ahat instance and heaps.
100     Collection<Heap> heaps = snapshot.getHeaps();
101     for (Heap heap : heaps) {
102       // Note: mHeaps will not be in index order if snapshot.getHeaps does not
103       // return heaps in index order. That's fine, because we don't rely on
104       // mHeaps being in index order.
105       mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap)));
106       TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
107         @Override
108         public boolean execute(Instance inst) {
109           long id = inst.getId();
110           if (inst instanceof ClassInstance) {
111             mInstances.add(new AhatClassInstance(id));
112           } else if (inst instanceof ArrayInstance) {
113             mInstances.add(new AhatArrayInstance(id));
114           } else if (inst instanceof ClassObj) {
115             AhatClassObj classObj = new AhatClassObj(id);
116             mInstances.add(classObj);
117             mClasses.put(((ClassObj)inst).getClassName(), classObj);
118           }
119           return true;
120         }
121       };
122       for (Instance instance : heap.getClasses()) {
123         doCreate.execute(instance);
124       }
125       heap.forEachInstance(doCreate);
126     }
127 
128     // Sort the instances by id so we can use binary search to lookup
129     // instances by id.
130     mInstances.sort(new Comparator<AhatInstance>() {
131       @Override
132       public int compare(AhatInstance a, AhatInstance b) {
133         return Long.compare(a.getId(), b.getId());
134       }
135     });
136 
137     // Initialize ahat snapshot and instances based on the perflib snapshot
138     // and instances.
139     for (AhatInstance ahat : mInstances) {
140       Instance inst = snapshot.findInstance(ahat.getId());
141       ahat.initialize(this, inst);
142 
143       if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) {
144         mRooted.add(ahat);
145       }
146 
147       if (inst.isReachable()) {
148         ahat.getHeap().addToSize(ahat.getSize());
149       }
150 
151       // Update sites.
152       StackFrame[] frames = null;
153       StackTrace stack = inst.getStack();
154       if (stack != null) {
155         frames = stack.getFrames();
156       }
157       Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
158       ahat.setSite(site);
159     }
160 
161     // Record the roots and their types.
162     for (RootObj root : snapshot.getGCRoots()) {
163       Instance inst = root.getReferredInstance();
164       if (inst != null) {
165         findInstance(inst.getId()).addRootType(root.getRootType().toString());
166       }
167     }
168     snapshot.dispose();
169   }
170 
171   /**
172    * Returns the instance with given id in this snapshot.
173    * Returns null if no instance with the given id is found.
174    */
findInstance(long id)175   public AhatInstance findInstance(long id) {
176     // Binary search over the sorted instances.
177     int start = 0;
178     int end = mInstances.size();
179     while (start < end) {
180       int mid = start + ((end - start) / 2);
181       AhatInstance midInst = mInstances.get(mid);
182       long midId = midInst.getId();
183       if (id == midId) {
184         return midInst;
185       } else if (id < midId) {
186         end = mid;
187       } else {
188         start = mid + 1;
189       }
190     }
191     return null;
192   }
193 
194   /**
195    * Returns the AhatClassObj with given id in this snapshot.
196    * Returns null if no class object with the given id is found.
197    */
findClassObj(long id)198   public AhatClassObj findClassObj(long id) {
199     AhatInstance inst = findInstance(id);
200     return inst == null ? null : inst.asClassObj();
201   }
202 
203   /**
204    * Returns the class object for the class with given name.
205    * Returns null if there is no class object for the given name.
206    * Note: This method is exposed for testing purposes.
207    */
findClass(String name)208   public AhatClassObj findClass(String name) {
209     return mClasses.get(name);
210   }
211 
212   /**
213    * Returns the heap with the given name, if any.
214    * Returns null if no heap with the given name could be found.
215    */
getHeap(String name)216   public AhatHeap getHeap(String name) {
217     // We expect a small number of heaps (maybe 3 or 4 total), so a linear
218     // search should be acceptable here performance wise.
219     for (AhatHeap heap : getHeaps()) {
220       if (heap.getName().equals(name)) {
221         return heap;
222       }
223     }
224     return null;
225   }
226 
227   /**
228    * Returns a list of heaps in the snapshot in canonical order.
229    * Modifications to the returned list are visible to this AhatSnapshot,
230    * which is used by diff to insert place holder heaps.
231    */
getHeaps()232   public List<AhatHeap> getHeaps() {
233     return mHeaps;
234   }
235 
236   /**
237    * Returns a collection of instances whose immediate dominator is the
238    * SENTINEL_ROOT.
239    */
getRooted()240   public List<AhatInstance> getRooted() {
241     return mRooted;
242   }
243 
244   /**
245    * Returns the root site for this snapshot.
246    */
getRootSite()247   public Site getRootSite() {
248     return mRootSite;
249   }
250 
251   // Get the site associated with the given id and depth.
252   // Returns the root site if no such site found.
getSite(int id, int depth)253   public Site getSite(int id, int depth) {
254     AhatInstance obj = findInstance(id);
255     if (obj == null) {
256       return mRootSite;
257     }
258 
259     Site site = obj.getSite();
260     for (int i = 0; i < depth && site.getParent() != null; i++) {
261       site = site.getParent();
262     }
263     return site;
264   }
265 
266   // Return the Value for the given perflib value object.
getValue(Object value)267   Value getValue(Object value) {
268     if (value instanceof Instance) {
269       value = findInstance(((Instance)value).getId());
270     }
271     return value == null ? null : new Value(value);
272   }
273 
setBaseline(AhatSnapshot baseline)274   public void setBaseline(AhatSnapshot baseline) {
275     mBaseline = baseline;
276   }
277 
278   /**
279    * Returns true if this snapshot has been diffed against another, different
280    * snapshot.
281    */
isDiffed()282   public boolean isDiffed() {
283     return mBaseline != this;
284   }
285 
getBaseline()286   @Override public AhatSnapshot getBaseline() {
287     return mBaseline;
288   }
289 
isPlaceHolder()290   @Override public boolean isPlaceHolder() {
291     return false;
292   }
293 }
294