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; 18 19 import com.android.tools.perflib.heap.ClassObj; 20 import com.android.tools.perflib.heap.Heap; 21 import com.android.tools.perflib.heap.Instance; 22 import com.android.tools.perflib.heap.RootObj; 23 import com.android.tools.perflib.heap.RootType; 24 import com.android.tools.perflib.heap.Snapshot; 25 import com.android.tools.perflib.heap.StackFrame; 26 import com.android.tools.perflib.heap.StackTrace; 27 import com.android.tools.perflib.captures.MemoryMappedFileBuffer; 28 import com.google.common.collect.Iterables; 29 import com.google.common.collect.Lists; 30 import java.io.File; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Collection; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 41 /** 42 * A wrapper over the perflib snapshot that provides the behavior we use in 43 * ahat. 44 */ 45 class AhatSnapshot { 46 private final Snapshot mSnapshot; 47 private final List<Heap> mHeaps; 48 49 // Map from Instance to the list of Instances it immediately dominates. 50 private final Map<Instance, List<Instance>> mDominated 51 = new HashMap<Instance, List<Instance>>(); 52 53 // Collection of objects whose immediate dominator is the SENTINEL_ROOT. 54 private final List<Instance> mRooted = new ArrayList<Instance>(); 55 56 // Map from roots to their types. 57 // Instances are only included if they are roots, and the collection of root 58 // types is guaranteed to be non-empty. 59 private final Map<Instance, Collection<RootType>> mRoots 60 = new HashMap<Instance, Collection<RootType>>(); 61 62 private final Site mRootSite = new Site("ROOT"); 63 private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>(); 64 65 private final List<InstanceUtils.NativeAllocation> mNativeAllocations 66 = new ArrayList<InstanceUtils.NativeAllocation>(); 67 68 /** 69 * Create an AhatSnapshot from an hprof file. 70 */ 71 public static AhatSnapshot fromHprof(File hprof) throws IOException { 72 Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprof)); 73 snapshot.computeDominators(); 74 return new AhatSnapshot(snapshot); 75 } 76 77 /** 78 * Construct an AhatSnapshot for the given perflib snapshot. 79 * Ther user is responsible for calling snapshot.computeDominators before 80 * calling this AhatSnapshot constructor. 81 */ 82 private AhatSnapshot(Snapshot snapshot) { 83 mSnapshot = snapshot; 84 mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps()); 85 86 ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class"); 87 for (Heap heap : mHeaps) { 88 long total = 0; 89 for (Instance inst : Iterables.concat(heap.getClasses(), heap.getInstances())) { 90 Instance dominator = inst.getImmediateDominator(); 91 if (dominator != null) { 92 total += inst.getSize(); 93 94 if (dominator == Snapshot.SENTINEL_ROOT) { 95 mRooted.add(inst); 96 } 97 98 // Properly label the class of a class object. 99 if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) { 100 inst.setClassId(javaLangClass.getId()); 101 } 102 103 // Update dominated instances. 104 List<Instance> instances = mDominated.get(dominator); 105 if (instances == null) { 106 instances = new ArrayList<Instance>(); 107 mDominated.put(dominator, instances); 108 } 109 instances.add(inst); 110 111 // Update sites. 112 List<StackFrame> path = Collections.emptyList(); 113 StackTrace stack = getStack(inst); 114 int stackId = getStackTraceSerialNumber(stack); 115 if (stack != null) { 116 StackFrame[] frames = getStackFrames(stack); 117 if (frames != null && frames.length > 0) { 118 path = Lists.reverse(Arrays.asList(frames)); 119 } 120 } 121 mRootSite.add(stackId, 0, path.iterator(), inst); 122 123 // Update native allocations. 124 InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst); 125 if (alloc != null) { 126 mNativeAllocations.add(alloc); 127 } 128 } 129 } 130 mHeapSizes.put(heap, total); 131 } 132 133 // Record the roots and their types. 134 for (RootObj root : snapshot.getGCRoots()) { 135 Instance inst = root.getReferredInstance(); 136 Collection<RootType> types = mRoots.get(inst); 137 if (types == null) { 138 types = new HashSet<RootType>(); 139 mRoots.put(inst, types); 140 } 141 types.add(root.getRootType()); 142 } 143 } 144 145 // Note: This method is exposed for testing purposes. 146 public ClassObj findClass(String name) { 147 return mSnapshot.findClass(name); 148 } 149 150 public Instance findInstance(long id) { 151 return mSnapshot.findInstance(id); 152 } 153 154 public int getHeapIndex(Heap heap) { 155 return mSnapshot.getHeapIndex(heap); 156 } 157 158 public Heap getHeap(String name) { 159 return mSnapshot.getHeap(name); 160 } 161 162 /** 163 * Returns a collection of instances whose immediate dominator is the 164 * SENTINEL_ROOT. 165 */ 166 public List<Instance> getRooted() { 167 return mRooted; 168 } 169 170 /** 171 * Returns true if the given instance is a root. 172 */ 173 public boolean isRoot(Instance inst) { 174 return mRoots.containsKey(inst); 175 } 176 177 /** 178 * Returns the list of root types for the given instance, or null if the 179 * instance is not a root. 180 */ 181 public Collection<RootType> getRootTypes(Instance inst) { 182 return mRoots.get(inst); 183 } 184 185 public List<Heap> getHeaps() { 186 return mHeaps; 187 } 188 189 public Site getRootSite() { 190 return mRootSite; 191 } 192 193 /** 194 * Look up the site at which the given object was allocated. 195 */ 196 public Site getSiteForInstance(Instance inst) { 197 Site site = mRootSite; 198 StackTrace stack = getStack(inst); 199 if (stack != null) { 200 StackFrame[] frames = getStackFrames(stack); 201 if (frames != null) { 202 List<StackFrame> path = Lists.reverse(Arrays.asList(frames)); 203 site = mRootSite.getChild(path.iterator()); 204 } 205 } 206 return site; 207 } 208 209 /** 210 * Return a list of those objects immediately dominated by the given 211 * instance. 212 */ 213 public List<Instance> getDominated(Instance inst) { 214 return mDominated.get(inst); 215 } 216 217 /** 218 * Return the total size of reachable objects allocated on the given heap. 219 */ 220 public long getHeapSize(Heap heap) { 221 return mHeapSizes.get(heap); 222 } 223 224 /** 225 * Return the class name for the given class object. 226 * classObj may be null, in which case "(class unknown)" is returned. 227 */ 228 public static String getClassName(ClassObj classObj) { 229 if (classObj == null) { 230 return "(class unknown)"; 231 } 232 return classObj.getClassName(); 233 } 234 235 // Return the stack where the given instance was allocated. 236 private static StackTrace getStack(Instance inst) { 237 return inst.getStack(); 238 } 239 240 // Return the list of stack frames for a stack trace. 241 private static StackFrame[] getStackFrames(StackTrace stack) { 242 return stack.getFrames(); 243 } 244 245 // Return the serial number of the given stack trace. 246 private static int getStackTraceSerialNumber(StackTrace stack) { 247 return stack.getSerialNumber(); 248 } 249 250 // Get the site associated with the given stack id and depth. 251 // Returns the root site if no such site found. 252 // depth of -1 means the full stack. 253 public Site getSite(int stackId, int depth) { 254 Site site = mRootSite; 255 StackTrace stack = mSnapshot.getStackTrace(stackId); 256 if (stack != null) { 257 StackFrame[] frames = getStackFrames(stack); 258 if (frames != null) { 259 List<StackFrame> path = Lists.reverse(Arrays.asList(frames)); 260 if (depth >= 0) { 261 path = path.subList(0, depth); 262 } 263 site = mRootSite.getChild(path.iterator()); 264 } 265 } 266 return site; 267 } 268 269 // Return a list of known native allocations in the snapshot. 270 public List<InstanceUtils.NativeAllocation> getNativeAllocations() { 271 return mNativeAllocations; 272 } 273 } 274