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
17package com.android.ahat;
18
19import com.android.tools.perflib.heap.ClassObj;
20import com.android.tools.perflib.heap.Heap;
21import com.android.tools.perflib.heap.Instance;
22import com.android.tools.perflib.heap.RootObj;
23import com.android.tools.perflib.heap.RootType;
24import com.android.tools.perflib.heap.Snapshot;
25import com.android.tools.perflib.heap.StackFrame;
26import com.android.tools.perflib.heap.StackTrace;
27import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
28import com.google.common.collect.Iterables;
29import com.google.common.collect.Lists;
30import java.io.File;
31import java.io.IOException;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.Collection;
35import java.util.Collections;
36import java.util.HashMap;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Map;
40
41/**
42 * A wrapper over the perflib snapshot that provides the behavior we use in
43 * ahat.
44 */
45class 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