/*
* Copyright (C) 2015 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;
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.RootObj;
import com.android.tools.perflib.heap.RootType;
import com.android.tools.perflib.heap.Snapshot;
import com.android.tools.perflib.heap.StackFrame;
import com.android.tools.perflib.heap.StackTrace;
import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* A wrapper over the perflib snapshot that provides the behavior we use in
* ahat.
*/
class AhatSnapshot {
private final Snapshot mSnapshot;
private final List<Heap> mHeaps;
// Map from Instance to the list of Instances it immediately dominates.
private final Map<Instance, List<Instance>> mDominated
= new HashMap<Instance, List<Instance>>();
// Collection of objects whose immediate dominator is the SENTINEL_ROOT.
private final List<Instance> mRooted = new ArrayList<Instance>();
// Map from roots to their types.
// Instances are only included if they are roots, and the collection of root
// types is guaranteed to be non-empty.
private final Map<Instance, Collection<RootType>> mRoots
= new HashMap<Instance, Collection<RootType>>();
private final Site mRootSite = new Site("ROOT");
private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>();
private final List<InstanceUtils.NativeAllocation> mNativeAllocations
= new ArrayList<InstanceUtils.NativeAllocation>();
/**
* Create an AhatSnapshot from an hprof file.
*/
public static AhatSnapshot fromHprof(File hprof) throws IOException {
Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprof));
snapshot.computeDominators();
return new AhatSnapshot(snapshot);
}
/**
* Construct an AhatSnapshot for the given perflib snapshot.
* Ther user is responsible for calling snapshot.computeDominators before
* calling this AhatSnapshot constructor.
*/
private AhatSnapshot(Snapshot snapshot) {
mSnapshot = snapshot;
mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
for (Heap heap : mHeaps) {
long total = 0;
for (Instance inst : Iterables.concat(heap.getClasses(), heap.getInstances())) {
Instance dominator = inst.getImmediateDominator();
if (dominator != null) {
total += inst.getSize();
if (dominator == Snapshot.SENTINEL_ROOT) {
mRooted.add(inst);
}
// Properly label the class of a class object.
if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) {
inst.setClassId(javaLangClass.getId());
}
// Update dominated instances.
List<Instance> instances = mDominated.get(dominator);
if (instances == null) {
instances = new ArrayList<Instance>();
mDominated.put(dominator, instances);
}
instances.add(inst);
// Update sites.
List<StackFrame> path = Collections.emptyList();
StackTrace stack = getStack(inst);
int stackId = getStackTraceSerialNumber(stack);
if (stack != null) {
StackFrame[] frames = getStackFrames(stack);
if (frames != null && frames.length > 0) {
path = Lists.reverse(Arrays.asList(frames));
}
}
mRootSite.add(stackId, 0, path.iterator(), inst);
// Update native allocations.
InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst);
if (alloc != null) {
mNativeAllocations.add(alloc);
}
}
}
mHeapSizes.put(heap, total);
}
// Record the roots and their types.
for (RootObj root : snapshot.getGCRoots()) {
Instance inst = root.getReferredInstance();
Collection<RootType> types = mRoots.get(inst);
if (types == null) {
types = new HashSet<RootType>();
mRoots.put(inst, types);
}
types.add(root.getRootType());
}
}
// Note: This method is exposed for testing purposes.
public ClassObj findClass(String name) {
return mSnapshot.findClass(name);
}
public Instance findInstance(long id) {
return mSnapshot.findInstance(id);
}
public int getHeapIndex(Heap heap) {
return mSnapshot.getHeapIndex(heap);
}
public Heap getHeap(String name) {
return mSnapshot.getHeap(name);
}
/**
* Returns a collection of instances whose immediate dominator is the
* SENTINEL_ROOT.
*/
public List<Instance> getRooted() {
return mRooted;
}
/**
* Returns true if the given instance is a root.
*/
public boolean isRoot(Instance inst) {
return mRoots.containsKey(inst);
}
/**
* Returns the list of root types for the given instance, or null if the
* instance is not a root.
*/
public Collection<RootType> getRootTypes(Instance inst) {
return mRoots.get(inst);
}
public List<Heap> getHeaps() {
return mHeaps;
}
public Site getRootSite() {
return mRootSite;
}
/**
* Look up the site at which the given object was allocated.
*/
public Site getSiteForInstance(Instance inst) {
Site site = mRootSite;
StackTrace stack = getStack(inst);
if (stack != null) {
StackFrame[] frames = getStackFrames(stack);
if (frames != null) {
List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
site = mRootSite.getChild(path.iterator());
}
}
return site;
}
/**
* Return a list of those objects immediately dominated by the given
* instance.
*/
public List<Instance> getDominated(Instance inst) {
return mDominated.get(inst);
}
/**
* Return the total size of reachable objects allocated on the given heap.
*/
public long getHeapSize(Heap heap) {
return mHeapSizes.get(heap);
}
/**
* Return the class name for the given class object.
* classObj may be null, in which case "(class unknown)" is returned.
*/
public static String getClassName(ClassObj classObj) {
if (classObj == null) {
return "(class unknown)";
}
return classObj.getClassName();
}
// Return the stack where the given instance was allocated.
private static StackTrace getStack(Instance inst) {
return inst.getStack();
}
// Return the list of stack frames for a stack trace.
private static StackFrame[] getStackFrames(StackTrace stack) {
return stack.getFrames();
}
// Return the serial number of the given stack trace.
private static int getStackTraceSerialNumber(StackTrace stack) {
return stack.getSerialNumber();
}
// Get the site associated with the given stack id and depth.
// Returns the root site if no such site found.
// depth of -1 means the full stack.
public Site getSite(int stackId, int depth) {
Site site = mRootSite;
StackTrace stack = mSnapshot.getStackTrace(stackId);
if (stack != null) {
StackFrame[] frames = getStackFrames(stack);
if (frames != null) {
List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
if (depth >= 0) {
path = path.subList(0, depth);
}
site = mRootSite.getChild(path.iterator());
}
}
return site;
}
// Return a list of known native allocations in the snapshot.
public List<InstanceUtils.NativeAllocation> getNativeAllocations() {
return mNativeAllocations;
}
}