/* * 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; } }