Home | History | Annotate | Download | only in heapdump
      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    */
     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    */
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    240   public List<AhatInstance> getRooted() {
    241     return mRooted;
    242   }
    243 
    244   /**
    245    * Returns the root site for this snapshot.
    246    */
    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.
    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.
    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 
    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    */
    282   public boolean isDiffed() {
    283     return mBaseline != this;
    284   }
    285 
    286   @Override public AhatSnapshot getBaseline() {
    287     return mBaseline;
    288   }
    289 
    290   @Override public boolean isPlaceHolder() {
    291     return false;
    292   }
    293 }
    294