Home | History | Annotate | Download | only in heapdump
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ahat.heapdump;
     18 
     19 import java.util.ArrayDeque;
     20 import java.util.ArrayList;
     21 import java.util.Collections;
     22 import java.util.Deque;
     23 import java.util.HashMap;
     24 import java.util.List;
     25 import java.util.Map;
     26 import java.util.Objects;
     27 
     28 /**
     29  * Provides a static method to diff two heap dumps.
     30  */
     31 public class Diff {
     32   private Diff() {
     33   }
     34 
     35   /**
     36    * Performs a diff between two heap lists.
     37    *
     38    * Heaps are diffed based on heap name. PlaceHolder heaps will be added to
     39    * the given lists as necessary so that every heap in A has a corresponding
     40    * heap in B and vice-versa.
     41    */
     42   private static void heaps(List<AhatHeap> a, List<AhatHeap> b) {
     43     int asize = a.size();
     44     int bsize = b.size();
     45     for (int i = 0; i < bsize; i++) {
     46       // Set the B heap's baseline as null to mark that we have not yet
     47       // matched it with an A heap.
     48       b.get(i).setBaseline(null);
     49     }
     50 
     51     for (int i = 0; i < asize; i++) {
     52       AhatHeap aheap = a.get(i);
     53       aheap.setBaseline(null);
     54       for (int j = 0; j < bsize; j++) {
     55         AhatHeap bheap = b.get(j);
     56         if (bheap.getBaseline() == null && aheap.getName().equals(bheap.getName())) {
     57           // We found a match between aheap and bheap.
     58           aheap.setBaseline(bheap);
     59           bheap.setBaseline(aheap);
     60           break;
     61         }
     62       }
     63 
     64       if (aheap.getBaseline() == null) {
     65         // We did not find any match for aheap in snapshot B.
     66         // Create a placeholder heap in snapshot B to use as the baseline.
     67         b.add(AhatHeap.newPlaceHolderHeap(aheap.getName(), aheap));
     68       }
     69     }
     70 
     71     // Make placeholder heaps in snapshot A for any unmatched heaps in
     72     // snapshot B.
     73     for (int i = 0; i < bsize; i++) {
     74       AhatHeap bheap = b.get(i);
     75       if (bheap.getBaseline() == null) {
     76         a.add(AhatHeap.newPlaceHolderHeap(bheap.getName(), bheap));
     77       }
     78     }
     79   }
     80 
     81   /**
     82    * Key represents an equivalence class of AhatInstances that are allowed to
     83    * be considered for correspondence between two different snapshots.
     84    */
     85   private static class Key {
     86     // Corresponding objects must belong to classes of the same name.
     87     private final String mClass;
     88 
     89     // Corresponding objects must belong to heaps of the same name.
     90     private final String mHeapName;
     91 
     92     // Corresponding string objects must have the same value.
     93     // mStringValue is set to the empty string for non-string objects.
     94     private final String mStringValue;
     95 
     96     // Corresponding class objects must have the same class name.
     97     // mClassName is set to the empty string for non-class objects.
     98     private final String mClassName;
     99 
    100     // Corresponding array objects must have the same length.
    101     // mArrayLength is set to 0 for non-array objects.
    102     private final int mArrayLength;
    103 
    104 
    105     private Key(AhatInstance inst) {
    106       mClass = inst.getClassName();
    107       mHeapName = inst.getHeap().getName();
    108       mClassName = inst.isClassObj() ? inst.asClassObj().getName() : "";
    109       String string = inst.asString();
    110       mStringValue = string == null ? "" : string;
    111       AhatArrayInstance array = inst.asArrayInstance();
    112       mArrayLength = array == null ? 0 : array.getLength();
    113     }
    114 
    115     /**
    116      * Return the key for the given instance.
    117      */
    118     public static Key keyFor(AhatInstance inst) {
    119       return new Key(inst);
    120     }
    121 
    122     @Override
    123     public boolean equals(Object other) {
    124       if (!(other instanceof Key)) {
    125         return false;
    126       }
    127       Key o = (Key)other;
    128       return mClass.equals(o.mClass)
    129           && mHeapName.equals(o.mHeapName)
    130           && mStringValue.equals(o.mStringValue)
    131           && mClassName.equals(o.mClassName)
    132           && mArrayLength == o.mArrayLength;
    133     }
    134 
    135     @Override
    136     public int hashCode() {
    137       return Objects.hash(mClass, mHeapName, mStringValue, mClassName, mArrayLength);
    138     }
    139   }
    140 
    141   private static class InstanceListPair {
    142     public final List<AhatInstance> a;
    143     public final List<AhatInstance> b;
    144 
    145     public InstanceListPair() {
    146       this.a = new ArrayList<AhatInstance>();
    147       this.b = new ArrayList<AhatInstance>();
    148     }
    149 
    150     public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) {
    151       this.a = a;
    152       this.b = b;
    153     }
    154   }
    155 
    156   /**
    157    * Recursively create place holder instances for the given instance and
    158    * every instance dominated by that instance.
    159    * Returns the place holder instance created for the given instance.
    160    * Adds all allocated placeholders to the given placeholders list.
    161    */
    162   private static AhatInstance createPlaceHolders(AhatInstance inst,
    163       List<AhatInstance> placeholders) {
    164     // Don't actually use recursion, because we could easily smash the stack.
    165     // Instead we iterate.
    166     AhatInstance result = inst.newPlaceHolderInstance();
    167     placeholders.add(result);
    168     Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>();
    169     deque.push(inst);
    170     while (!deque.isEmpty()) {
    171       inst = deque.pop();
    172 
    173       for (AhatInstance child : inst.getDominated()) {
    174         placeholders.add(child.newPlaceHolderInstance());
    175         deque.push(child);
    176       }
    177     }
    178     return result;
    179   }
    180 
    181   /**
    182    * Recursively diff two dominator trees of instances.
    183    * PlaceHolder objects are appended to the lists as needed to ensure every
    184    * object has a corresponding baseline in the other list. All PlaceHolder
    185    * objects are also appended to the given placeholders list, so their Site
    186    * info can be updated later on.
    187    */
    188   private static void instances(List<AhatInstance> a, List<AhatInstance> b,
    189       List<AhatInstance> placeholders) {
    190     // Don't actually use recursion, because we could easily smash the stack.
    191     // Instead we iterate.
    192     Deque<InstanceListPair> deque = new ArrayDeque<InstanceListPair>();
    193     deque.push(new InstanceListPair(a, b));
    194     while (!deque.isEmpty()) {
    195       InstanceListPair p = deque.pop();
    196 
    197       // Group instances of the same equivalence class together.
    198       Map<Key, InstanceListPair> byKey = new HashMap<Key, InstanceListPair>();
    199       for (AhatInstance inst : p.a) {
    200         Key key = Key.keyFor(inst);
    201         InstanceListPair pair = byKey.get(key);
    202         if (pair == null) {
    203           pair = new InstanceListPair();
    204           byKey.put(key, pair);
    205         }
    206         pair.a.add(inst);
    207       }
    208       for (AhatInstance inst : p.b) {
    209         Key key = Key.keyFor(inst);
    210         InstanceListPair pair = byKey.get(key);
    211         if (pair == null) {
    212           pair = new InstanceListPair();
    213           byKey.put(key, pair);
    214         }
    215         pair.b.add(inst);
    216       }
    217 
    218       // diff objects from the same key class.
    219       for (InstanceListPair pair : byKey.values()) {
    220         // Sort by retained size and assume the elements at the top of the lists
    221         // correspond to each other in that order. This could probably be
    222         // improved if desired, but it gives good enough results for now.
    223         Collections.sort(pair.a, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
    224         Collections.sort(pair.b, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
    225 
    226         int common = Math.min(pair.a.size(), pair.b.size());
    227         for (int i = 0; i < common; i++) {
    228           AhatInstance ainst = pair.a.get(i);
    229           AhatInstance binst = pair.b.get(i);
    230           ainst.setBaseline(binst);
    231           binst.setBaseline(ainst);
    232           deque.push(new InstanceListPair(ainst.getDominated(), binst.getDominated()));
    233         }
    234 
    235         // Add placeholder objects for anything leftover.
    236         for (int i = common; i < pair.a.size(); i++) {
    237           p.b.add(createPlaceHolders(pair.a.get(i), placeholders));
    238         }
    239 
    240         for (int i = common; i < pair.b.size(); i++) {
    241           p.a.add(createPlaceHolders(pair.b.get(i), placeholders));
    242         }
    243       }
    244     }
    245   }
    246 
    247   /**
    248    * Sets the baseline for root and all its descendants to baseline.
    249    */
    250   private static void setSitesBaseline(Site root, Site baseline) {
    251     root.setBaseline(baseline);
    252     for (Site child : root.getChildren()) {
    253       setSitesBaseline(child, baseline);
    254     }
    255   }
    256 
    257   /**
    258    * Recursively diff the two sites, setting them and their descendants as
    259    * baselines for each other as appropriate.
    260    *
    261    * This requires that instances have already been diffed. In particular, we
    262    * require all AhatClassObjs in one snapshot have corresponding (possibly
    263    * place-holder) AhatClassObjs in the other snapshot.
    264    */
    265   private static void sites(Site a, Site b) {
    266     // Set the sites as baselines of each other.
    267     a.setBaseline(b);
    268     b.setBaseline(a);
    269 
    270     // Set the site's ObjectsInfos as baselines of each other. This implicitly
    271     // adds new empty ObjectsInfo as needed.
    272     for (Site.ObjectsInfo ainfo : a.getObjectsInfos()) {
    273       AhatClassObj baseClassObj = null;
    274       if (ainfo.classObj != null) {
    275         baseClassObj = (AhatClassObj) ainfo.classObj.getBaseline();
    276       }
    277       ainfo.setBaseline(b.getObjectsInfo(ainfo.heap.getBaseline(), baseClassObj));
    278     }
    279     for (Site.ObjectsInfo binfo : b.getObjectsInfos()) {
    280       AhatClassObj baseClassObj = null;
    281       if (binfo.classObj != null) {
    282         baseClassObj = (AhatClassObj) binfo.classObj.getBaseline();
    283       }
    284       binfo.setBaseline(a.getObjectsInfo(binfo.heap.getBaseline(), baseClassObj));
    285     }
    286 
    287     // Set B children's baselines as null to mark that we have not yet matched
    288     // them with A children.
    289     for (Site bchild : b.getChildren()) {
    290       bchild.setBaseline(null);
    291     }
    292 
    293     for (Site achild : a.getChildren()) {
    294       achild.setBaseline(null);
    295       for (Site bchild : b.getChildren()) {
    296         if (achild.getLineNumber() == bchild.getLineNumber()
    297             && achild.getMethodName().equals(bchild.getMethodName())
    298             && achild.getSignature().equals(bchild.getSignature())
    299             && achild.getFilename().equals(bchild.getFilename())) {
    300           // We found a match between achild and bchild.
    301           sites(achild, bchild);
    302           break;
    303         }
    304       }
    305 
    306       if (achild.getBaseline() == null) {
    307         // We did not find any match for achild in site B.
    308         // Use B for the baseline of achild and its descendants.
    309         setSitesBaseline(achild, b);
    310       }
    311     }
    312 
    313     for (Site bchild : b.getChildren()) {
    314       if (bchild.getBaseline() == null) {
    315         setSitesBaseline(bchild, a);
    316       }
    317     }
    318   }
    319 
    320   /**
    321    * Performs a diff of two snapshots.
    322    * Each snapshot will be set as the baseline for the other snapshot.
    323    * <p>
    324    * The diff algorithm attempts to match instances in snapshot <code>a</code>
    325    * to corresponding instances in snapshot <code>b</code>. The snapshots need
    326    * not come from the same running process, application version, or platform
    327    * version.
    328    *
    329    * @param a one of the snapshots to diff
    330    * @param b the other of the snapshots to diff
    331    */
    332   public static void snapshots(AhatSnapshot a, AhatSnapshot b) {
    333     a.setBaseline(b);
    334     b.setBaseline(a);
    335 
    336     // Diff the heaps of each snapshot.
    337     heaps(a.getHeaps(), b.getHeaps());
    338 
    339     // Diff the instances of each snapshot.
    340     List<AhatInstance> placeholders = new ArrayList<AhatInstance>();
    341     instances(a.getRooted(), b.getRooted(), placeholders);
    342 
    343     // Diff the sites of each snapshot.
    344     // This requires the instances have already been diffed.
    345     sites(a.getRootSite(), b.getRootSite());
    346 
    347     // Add placeholders to their corresponding sites.
    348     // This requires the sites have already been diffed.
    349     for (AhatInstance placeholder : placeholders) {
    350       placeholder.getBaseline().getSite().getBaseline().addInstance(placeholder);
    351     }
    352   }
    353 }
    354