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 public class Diff { 29 /** 30 * Perform a diff between two heap lists. 31 * 32 * Heaps are diffed based on heap name. PlaceHolder heaps will be added to 33 * the given lists as necessary so that every heap in A has a corresponding 34 * heap in B and vice-versa. 35 */ heaps(List<AhatHeap> a, List<AhatHeap> b)36 private static void heaps(List<AhatHeap> a, List<AhatHeap> b) { 37 int asize = a.size(); 38 int bsize = b.size(); 39 for (int i = 0; i < bsize; i++) { 40 // Set the B heap's baseline as null to mark that we have not yet 41 // matched it with an A heap. 42 b.get(i).setBaseline(null); 43 } 44 45 for (int i = 0; i < asize; i++) { 46 AhatHeap aheap = a.get(i); 47 aheap.setBaseline(null); 48 for (int j = 0; j < bsize; j++) { 49 AhatHeap bheap = b.get(j); 50 if (bheap.getBaseline() == null && aheap.getName().equals(bheap.getName())) { 51 // We found a match between aheap and bheap. 52 aheap.setBaseline(bheap); 53 bheap.setBaseline(aheap); 54 break; 55 } 56 } 57 58 if (aheap.getBaseline() == null) { 59 // We did not find any match for aheap in snapshot B. 60 // Create a placeholder heap in snapshot B to use as the baseline. 61 b.add(AhatHeap.newPlaceHolderHeap(aheap.getName(), aheap)); 62 } 63 } 64 65 // Make placeholder heaps in snapshot A for any unmatched heaps in 66 // snapshot B. 67 for (int i = 0; i < bsize; i++) { 68 AhatHeap bheap = b.get(i); 69 if (bheap.getBaseline() == null) { 70 a.add(AhatHeap.newPlaceHolderHeap(bheap.getName(), bheap)); 71 } 72 } 73 } 74 75 /** 76 * Key represents an equivalence class of AhatInstances that are allowed to 77 * be considered for correspondence between two different snapshots. 78 */ 79 private static class Key { 80 // Corresponding objects must belong to classes of the same name. 81 private final String mClass; 82 83 // Corresponding objects must belong to heaps of the same name. 84 private final String mHeapName; 85 86 // Corresponding string objects must have the same value. 87 // mStringValue is set to the empty string for non-string objects. 88 private final String mStringValue; 89 90 // Corresponding class objects must have the same class name. 91 // mClassName is set to the empty string for non-class objects. 92 private final String mClassName; 93 94 // Corresponding array objects must have the same length. 95 // mArrayLength is set to 0 for non-array objects. 96 private final int mArrayLength; 97 98 Key(AhatInstance inst)99 private Key(AhatInstance inst) { 100 mClass = inst.getClassName(); 101 mHeapName = inst.getHeap().getName(); 102 mClassName = inst.isClassObj() ? inst.asClassObj().getName() : ""; 103 String string = inst.asString(); 104 mStringValue = string == null ? "" : string; 105 AhatArrayInstance array = inst.asArrayInstance(); 106 mArrayLength = array == null ? 0 : array.getLength(); 107 } 108 109 /** 110 * Return the key for the given instance. 111 */ keyFor(AhatInstance inst)112 public static Key keyFor(AhatInstance inst) { 113 return new Key(inst); 114 } 115 116 @Override equals(Object other)117 public boolean equals(Object other) { 118 if (!(other instanceof Key)) { 119 return false; 120 } 121 Key o = (Key)other; 122 return mClass.equals(o.mClass) 123 && mHeapName.equals(o.mHeapName) 124 && mStringValue.equals(o.mStringValue) 125 && mClassName.equals(o.mClassName) 126 && mArrayLength == o.mArrayLength; 127 } 128 129 @Override hashCode()130 public int hashCode() { 131 return Objects.hash(mClass, mHeapName, mStringValue, mClassName, mArrayLength); 132 } 133 } 134 135 private static class InstanceListPair { 136 public final List<AhatInstance> a; 137 public final List<AhatInstance> b; 138 InstanceListPair()139 public InstanceListPair() { 140 this.a = new ArrayList<AhatInstance>(); 141 this.b = new ArrayList<AhatInstance>(); 142 } 143 InstanceListPair(List<AhatInstance> a, List<AhatInstance> b)144 public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) { 145 this.a = a; 146 this.b = b; 147 } 148 } 149 150 /** 151 * Recursively create place holder instances for the given instance and 152 * every instance dominated by that instance. 153 * Returns the place holder instance created for the given instance. 154 * Adds all allocated placeholders to the given placeholders list. 155 */ createPlaceHolders(AhatInstance inst, List<AhatInstance> placeholders)156 private static AhatInstance createPlaceHolders(AhatInstance inst, 157 List<AhatInstance> placeholders) { 158 // Don't actually use recursion, because we could easily smash the stack. 159 // Instead we iterate. 160 AhatInstance result = inst.newPlaceHolderInstance(); 161 placeholders.add(result); 162 Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>(); 163 deque.push(inst); 164 while (!deque.isEmpty()) { 165 inst = deque.pop(); 166 167 for (AhatInstance child : inst.getDominated()) { 168 placeholders.add(child.newPlaceHolderInstance()); 169 deque.push(child); 170 } 171 } 172 return result; 173 } 174 175 /** 176 * Recursively diff two dominator trees of instances. 177 * PlaceHolder objects are appended to the lists as needed to ensure every 178 * object has a corresponding baseline in the other list. All PlaceHolder 179 * objects are also appended to the given placeholders list, so their Site 180 * info can be updated later on. 181 */ instances(List<AhatInstance> a, List<AhatInstance> b, List<AhatInstance> placeholders)182 private static void instances(List<AhatInstance> a, List<AhatInstance> b, 183 List<AhatInstance> placeholders) { 184 // Don't actually use recursion, because we could easily smash the stack. 185 // Instead we iterate. 186 Deque<InstanceListPair> deque = new ArrayDeque<InstanceListPair>(); 187 deque.push(new InstanceListPair(a, b)); 188 while (!deque.isEmpty()) { 189 InstanceListPair p = deque.pop(); 190 191 // Group instances of the same equivalence class together. 192 Map<Key, InstanceListPair> byKey = new HashMap<Key, InstanceListPair>(); 193 for (AhatInstance inst : p.a) { 194 Key key = Key.keyFor(inst); 195 InstanceListPair pair = byKey.get(key); 196 if (pair == null) { 197 pair = new InstanceListPair(); 198 byKey.put(key, pair); 199 } 200 pair.a.add(inst); 201 } 202 for (AhatInstance inst : p.b) { 203 Key key = Key.keyFor(inst); 204 InstanceListPair pair = byKey.get(key); 205 if (pair == null) { 206 pair = new InstanceListPair(); 207 byKey.put(key, pair); 208 } 209 pair.b.add(inst); 210 } 211 212 // diff objects from the same key class. 213 for (InstanceListPair pair : byKey.values()) { 214 // Sort by retained size and assume the elements at the top of the lists 215 // correspond to each other in that order. This could probably be 216 // improved if desired, but it gives good enough results for now. 217 Collections.sort(pair.a, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE); 218 Collections.sort(pair.b, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE); 219 220 int common = Math.min(pair.a.size(), pair.b.size()); 221 for (int i = 0; i < common; i++) { 222 AhatInstance ainst = pair.a.get(i); 223 AhatInstance binst = pair.b.get(i); 224 ainst.setBaseline(binst); 225 binst.setBaseline(ainst); 226 deque.push(new InstanceListPair(ainst.getDominated(), binst.getDominated())); 227 } 228 229 // Add placeholder objects for anything leftover. 230 for (int i = common; i < pair.a.size(); i++) { 231 p.b.add(createPlaceHolders(pair.a.get(i), placeholders)); 232 } 233 234 for (int i = common; i < pair.b.size(); i++) { 235 p.a.add(createPlaceHolders(pair.b.get(i), placeholders)); 236 } 237 } 238 } 239 } 240 241 /** 242 * Sets the baseline for root and all its descendants to baseline. 243 */ setSitesBaseline(Site root, Site baseline)244 private static void setSitesBaseline(Site root, Site baseline) { 245 root.setBaseline(baseline); 246 for (Site child : root.getChildren()) { 247 setSitesBaseline(child, baseline); 248 } 249 } 250 251 /** 252 * Recursively diff the two sites, setting them and their descendants as 253 * baselines for each other as appropriate. 254 * 255 * This requires that instances have already been diffed. In particular, we 256 * require all AhatClassObjs in one snapshot have corresponding (possibly 257 * place-holder) AhatClassObjs in the other snapshot. 258 */ sites(Site a, Site b)259 private static void sites(Site a, Site b) { 260 // Set the sites as baselines of each other. 261 a.setBaseline(b); 262 b.setBaseline(a); 263 264 // Set the site's ObjectsInfos as baselines of each other. This implicitly 265 // adds new empty ObjectsInfo as needed. 266 for (Site.ObjectsInfo ainfo : a.getObjectsInfos()) { 267 AhatClassObj baseClassObj = null; 268 if (ainfo.classObj != null) { 269 baseClassObj = (AhatClassObj) ainfo.classObj.getBaseline(); 270 } 271 ainfo.setBaseline(b.getObjectsInfo(ainfo.heap.getBaseline(), baseClassObj)); 272 } 273 for (Site.ObjectsInfo binfo : b.getObjectsInfos()) { 274 AhatClassObj baseClassObj = null; 275 if (binfo.classObj != null) { 276 baseClassObj = (AhatClassObj) binfo.classObj.getBaseline(); 277 } 278 binfo.setBaseline(a.getObjectsInfo(binfo.heap.getBaseline(), baseClassObj)); 279 } 280 281 // Set B children's baselines as null to mark that we have not yet matched 282 // them with A children. 283 for (Site bchild : b.getChildren()) { 284 bchild.setBaseline(null); 285 } 286 287 for (Site achild : a.getChildren()) { 288 achild.setBaseline(null); 289 for (Site bchild : b.getChildren()) { 290 if (achild.getLineNumber() == bchild.getLineNumber() 291 && achild.getMethodName().equals(bchild.getMethodName()) 292 && achild.getSignature().equals(bchild.getSignature()) 293 && achild.getFilename().equals(bchild.getFilename())) { 294 // We found a match between achild and bchild. 295 sites(achild, bchild); 296 break; 297 } 298 } 299 300 if (achild.getBaseline() == null) { 301 // We did not find any match for achild in site B. 302 // Use B for the baseline of achild and its descendants. 303 setSitesBaseline(achild, b); 304 } 305 } 306 307 for (Site bchild : b.getChildren()) { 308 if (bchild.getBaseline() == null) { 309 setSitesBaseline(bchild, a); 310 } 311 } 312 } 313 314 /** 315 * Perform a diff of the two snapshots, setting each as the baseline for the 316 * other. 317 */ snapshots(AhatSnapshot a, AhatSnapshot b)318 public static void snapshots(AhatSnapshot a, AhatSnapshot b) { 319 a.setBaseline(b); 320 b.setBaseline(a); 321 322 // Diff the heaps of each snapshot. 323 heaps(a.getHeaps(), b.getHeaps()); 324 325 // Diff the instances of each snapshot. 326 List<AhatInstance> placeholders = new ArrayList<AhatInstance>(); 327 instances(a.getRooted(), b.getRooted(), placeholders); 328 329 // Diff the sites of each snapshot. 330 // This requires the instances have already been diffed. 331 sites(a.getRootSite(), b.getRootSite()); 332 333 // Add placeholders to their corresponding sites. 334 // This requires the sites have already been diffed. 335 for (AhatInstance placeholder : placeholders) { 336 placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder); 337 } 338 } 339 340 /** 341 * Diff two lists of field values. 342 * PlaceHolder objects are added to the given lists as needed to ensure 343 * every FieldValue in A ends up with a corresponding FieldValue in B. 344 */ fields(List<FieldValue> a, List<FieldValue> b)345 public static void fields(List<FieldValue> a, List<FieldValue> b) { 346 // Fields with the same name and type are considered matching fields. 347 // For simplicity, we assume the matching fields are in the same order in 348 // both A and B, though some fields may be added or removed in either 349 // list. If our assumption is wrong, in the worst case the quality of the 350 // field diff is poor. 351 352 for (int i = 0; i < a.size(); i++) { 353 FieldValue afield = a.get(i); 354 afield.setBaseline(null); 355 356 // Find the matching field in B, if any. 357 for (int j = i; j < b.size(); j++) { 358 FieldValue bfield = b.get(j); 359 if (afield.getName().equals(bfield.getName()) 360 && afield.getType().equals(bfield.getType())) { 361 // We found the matching field in B. 362 // Assume fields i, ..., j-1 in B have no match in A. 363 for ( ; i < j; i++) { 364 a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i))); 365 } 366 367 afield.setBaseline(bfield); 368 bfield.setBaseline(afield); 369 break; 370 } 371 } 372 373 if (afield.getBaseline() == null) { 374 b.add(i, FieldValue.newPlaceHolderFieldValue(afield)); 375 } 376 } 377 378 // All remaining fields in B are unmatched by any in A. 379 for (int i = a.size(); i < b.size(); i++) { 380 a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i))); 381 } 382 } 383 } 384