| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ahat.heapdump; |
| |
| import com.android.tools.perflib.captures.DataBuffer; |
| import com.android.tools.perflib.captures.MemoryMappedFileBuffer; |
| import com.android.tools.perflib.heap.ArrayInstance; |
| import com.android.tools.perflib.heap.ClassInstance; |
| import com.android.tools.perflib.heap.ClassObj; |
| import com.android.tools.perflib.heap.Heap; |
| import com.android.tools.perflib.heap.Instance; |
| import com.android.tools.perflib.heap.ProguardMap; |
| import com.android.tools.perflib.heap.RootObj; |
| import com.android.tools.perflib.heap.Snapshot; |
| import com.android.tools.perflib.heap.StackFrame; |
| import com.android.tools.perflib.heap.StackTrace; |
| import gnu.trove.TObjectProcedure; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class AhatSnapshot implements Diffable<AhatSnapshot> { |
| private final Site mRootSite = new Site("ROOT"); |
| |
| // Collection of objects whose immediate dominator is the SENTINEL_ROOT. |
| private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>(); |
| |
| // List of all ahat instances stored in increasing order by id. |
| private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>(); |
| |
| // Map from class name to class object. |
| private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>(); |
| |
| private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>(); |
| |
| private AhatSnapshot mBaseline = this; |
| |
| /** |
| * Create an AhatSnapshot from an hprof file. |
| */ |
| public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException { |
| return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map); |
| } |
| |
| /** |
| * Create an AhatSnapshot from an in-memory data buffer. |
| */ |
| public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException { |
| AhatSnapshot snapshot = new AhatSnapshot(buffer, map); |
| |
| // Request a GC now to clean up memory used by perflib. This helps to |
| // avoid a noticable pause when visiting the first interesting page in |
| // ahat. |
| System.gc(); |
| |
| return snapshot; |
| } |
| |
| /** |
| * Constructs an AhatSnapshot for the given hprof binary data. |
| */ |
| private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException { |
| Snapshot snapshot = Snapshot.createSnapshot(buffer, map); |
| snapshot.computeDominators(); |
| |
| // Properly label the class of class objects in the perflib snapshot, and |
| // count the total number of instances. |
| final ClassObj javaLangClass = snapshot.findClass("java.lang.Class"); |
| if (javaLangClass != null) { |
| for (Heap heap : snapshot.getHeaps()) { |
| Collection<ClassObj> classes = heap.getClasses(); |
| for (ClassObj clsObj : classes) { |
| if (clsObj.getClassObj() == null) { |
| clsObj.setClassId(javaLangClass.getId()); |
| } |
| } |
| } |
| } |
| |
| // Create mappings from id to ahat instance and heaps. |
| Collection<Heap> heaps = snapshot.getHeaps(); |
| for (Heap heap : heaps) { |
| // Note: mHeaps will not be in index order if snapshot.getHeaps does not |
| // return heaps in index order. That's fine, because we don't rely on |
| // mHeaps being in index order. |
| mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap))); |
| TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() { |
| @Override |
| public boolean execute(Instance inst) { |
| long id = inst.getId(); |
| if (inst instanceof ClassInstance) { |
| mInstances.add(new AhatClassInstance(id)); |
| } else if (inst instanceof ArrayInstance) { |
| mInstances.add(new AhatArrayInstance(id)); |
| } else if (inst instanceof ClassObj) { |
| AhatClassObj classObj = new AhatClassObj(id); |
| mInstances.add(classObj); |
| mClasses.put(((ClassObj)inst).getClassName(), classObj); |
| } |
| return true; |
| } |
| }; |
| for (Instance instance : heap.getClasses()) { |
| doCreate.execute(instance); |
| } |
| heap.forEachInstance(doCreate); |
| } |
| |
| // Sort the instances by id so we can use binary search to lookup |
| // instances by id. |
| mInstances.sort(new Comparator<AhatInstance>() { |
| @Override |
| public int compare(AhatInstance a, AhatInstance b) { |
| return Long.compare(a.getId(), b.getId()); |
| } |
| }); |
| |
| // Initialize ahat snapshot and instances based on the perflib snapshot |
| // and instances. |
| for (AhatInstance ahat : mInstances) { |
| Instance inst = snapshot.findInstance(ahat.getId()); |
| ahat.initialize(this, inst); |
| |
| if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) { |
| mRooted.add(ahat); |
| } |
| |
| if (inst.isReachable()) { |
| ahat.getHeap().addToSize(ahat.getSize()); |
| } |
| |
| // Update sites. |
| StackFrame[] frames = null; |
| StackTrace stack = inst.getStack(); |
| if (stack != null) { |
| frames = stack.getFrames(); |
| } |
| Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat); |
| ahat.setSite(site); |
| } |
| |
| // Record the roots and their types. |
| for (RootObj root : snapshot.getGCRoots()) { |
| Instance inst = root.getReferredInstance(); |
| if (inst != null) { |
| findInstance(inst.getId()).addRootType(root.getRootType().toString()); |
| } |
| } |
| snapshot.dispose(); |
| } |
| |
| /** |
| * Returns the instance with given id in this snapshot. |
| * Returns null if no instance with the given id is found. |
| */ |
| public AhatInstance findInstance(long id) { |
| // Binary search over the sorted instances. |
| int start = 0; |
| int end = mInstances.size(); |
| while (start < end) { |
| int mid = start + ((end - start) / 2); |
| AhatInstance midInst = mInstances.get(mid); |
| long midId = midInst.getId(); |
| if (id == midId) { |
| return midInst; |
| } else if (id < midId) { |
| end = mid; |
| } else { |
| start = mid + 1; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the AhatClassObj with given id in this snapshot. |
| * Returns null if no class object with the given id is found. |
| */ |
| public AhatClassObj findClassObj(long id) { |
| AhatInstance inst = findInstance(id); |
| return inst == null ? null : inst.asClassObj(); |
| } |
| |
| /** |
| * Returns the class object for the class with given name. |
| * Returns null if there is no class object for the given name. |
| * Note: This method is exposed for testing purposes. |
| */ |
| public AhatClassObj findClass(String name) { |
| return mClasses.get(name); |
| } |
| |
| /** |
| * Returns the heap with the given name, if any. |
| * Returns null if no heap with the given name could be found. |
| */ |
| public AhatHeap getHeap(String name) { |
| // We expect a small number of heaps (maybe 3 or 4 total), so a linear |
| // search should be acceptable here performance wise. |
| for (AhatHeap heap : getHeaps()) { |
| if (heap.getName().equals(name)) { |
| return heap; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a list of heaps in the snapshot in canonical order. |
| * Modifications to the returned list are visible to this AhatSnapshot, |
| * which is used by diff to insert place holder heaps. |
| */ |
| public List<AhatHeap> getHeaps() { |
| return mHeaps; |
| } |
| |
| /** |
| * Returns a collection of instances whose immediate dominator is the |
| * SENTINEL_ROOT. |
| */ |
| public List<AhatInstance> getRooted() { |
| return mRooted; |
| } |
| |
| /** |
| * Returns the root site for this snapshot. |
| */ |
| public Site getRootSite() { |
| return mRootSite; |
| } |
| |
| // Get the site associated with the given id and depth. |
| // Returns the root site if no such site found. |
| public Site getSite(int id, int depth) { |
| AhatInstance obj = findInstance(id); |
| if (obj == null) { |
| return mRootSite; |
| } |
| |
| Site site = obj.getSite(); |
| for (int i = 0; i < depth && site.getParent() != null; i++) { |
| site = site.getParent(); |
| } |
| return site; |
| } |
| |
| // Return the Value for the given perflib value object. |
| Value getValue(Object value) { |
| if (value instanceof Instance) { |
| value = findInstance(((Instance)value).getId()); |
| } |
| return value == null ? null : new Value(value); |
| } |
| |
| public void setBaseline(AhatSnapshot baseline) { |
| mBaseline = baseline; |
| } |
| |
| /** |
| * Returns true if this snapshot has been diffed against another, different |
| * snapshot. |
| */ |
| public boolean isDiffed() { |
| return mBaseline != this; |
| } |
| |
| @Override public AhatSnapshot getBaseline() { |
| return mBaseline; |
| } |
| |
| @Override public boolean isPlaceHolder() { |
| return false; |
| } |
| } |