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