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