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