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 { 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 */ heaps(List<AhatHeap> a, List<AhatHeap> b)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 Key(AhatInstance inst)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 */ keyFor(AhatInstance inst)118 public static Key keyFor(AhatInstance inst) { 119 return new Key(inst); 120 } 121 122 @Override equals(Object other)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 hashCode()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 InstanceListPair()145 public InstanceListPair() { 146 this.a = new ArrayList<AhatInstance>(); 147 this.b = new ArrayList<AhatInstance>(); 148 } 149 InstanceListPair(List<AhatInstance> a, List<AhatInstance> b)150 public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) { 151 this.a = a; 152 this.b = b; 153 } 154 } 155 156 /** 157 * Recursively create placeholder instances for the given instance and 158 * every instance dominated by that instance. 159 * Returns the placeholder instance created for the given instance. 160 * Adds all allocated placeholders to the given placeholders list. 161 */ createPlaceHolders(AhatInstance inst, List<AhatInstance> placeholders)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 */ instances(List<AhatInstance> a, List<AhatInstance> b, List<AhatInstance> placeholders)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 */ setSitesBaseline(Site root, Site baseline)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 * placeholder) AhatClassObjs in the other snapshot. 264 */ sites(Site a, Site b)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 */ snapshots(AhatSnapshot a, AhatSnapshot b)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