1 /* 2 * Copyright (C) 2015 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; 18 19 import com.android.ahat.heapdump.AhatClassObj; 20 import com.android.ahat.heapdump.AhatHeap; 21 import com.android.ahat.heapdump.AhatInstance; 22 import com.android.ahat.heapdump.AhatSnapshot; 23 import com.android.ahat.heapdump.PathElement; 24 import com.android.ahat.heapdump.Value; 25 import com.android.tools.perflib.heap.hprof.HprofClassDump; 26 import com.android.tools.perflib.heap.hprof.HprofConstant; 27 import com.android.tools.perflib.heap.hprof.HprofDumpRecord; 28 import com.android.tools.perflib.heap.hprof.HprofHeapDump; 29 import com.android.tools.perflib.heap.hprof.HprofInstanceDump; 30 import com.android.tools.perflib.heap.hprof.HprofInstanceField; 31 import com.android.tools.perflib.heap.hprof.HprofLoadClass; 32 import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump; 33 import com.android.tools.perflib.heap.hprof.HprofRecord; 34 import com.android.tools.perflib.heap.hprof.HprofRootDebugger; 35 import com.android.tools.perflib.heap.hprof.HprofStaticField; 36 import com.android.tools.perflib.heap.hprof.HprofStringBuilder; 37 import com.android.tools.perflib.heap.hprof.HprofType; 38 import com.google.common.io.ByteArrayDataOutput; 39 import com.google.common.io.ByteStreams; 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.List; 43 import org.junit.Test; 44 45 import static org.junit.Assert.assertEquals; 46 import static org.junit.Assert.assertFalse; 47 import static org.junit.Assert.assertNotNull; 48 import static org.junit.Assert.assertNull; 49 import static org.junit.Assert.assertTrue; 50 51 public class InstanceTest { 52 @Test 53 public void asStringBasic() throws IOException { 54 TestDump dump = TestDump.getTestDump(); 55 AhatInstance str = dump.getDumpedAhatInstance("basicString"); 56 assertEquals("hello, world", str.asString()); 57 } 58 59 @Test 60 public void asStringNonAscii() throws IOException { 61 TestDump dump = TestDump.getTestDump(); 62 AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); 63 assertEquals("Sigma () is not ASCII", str.asString()); 64 } 65 66 @Test 67 public void asStringEmbeddedZero() throws IOException { 68 TestDump dump = TestDump.getTestDump(); 69 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); 70 assertEquals("embedded\0...", str.asString()); 71 } 72 73 @Test 74 public void asStringCharArray() throws IOException { 75 TestDump dump = TestDump.getTestDump(); 76 AhatInstance str = dump.getDumpedAhatInstance("charArray"); 77 assertEquals("char thing", str.asString()); 78 } 79 80 @Test 81 public void asStringTruncated() throws IOException { 82 TestDump dump = TestDump.getTestDump(); 83 AhatInstance str = dump.getDumpedAhatInstance("basicString"); 84 assertEquals("hello", str.asString(5)); 85 } 86 87 @Test 88 public void asStringTruncatedNonAscii() throws IOException { 89 TestDump dump = TestDump.getTestDump(); 90 AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); 91 assertEquals("Sigma ()", str.asString(9)); 92 } 93 94 @Test 95 public void asStringTruncatedEmbeddedZero() throws IOException { 96 TestDump dump = TestDump.getTestDump(); 97 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); 98 assertEquals("embed", str.asString(5)); 99 } 100 101 @Test 102 public void asStringCharArrayTruncated() throws IOException { 103 TestDump dump = TestDump.getTestDump(); 104 AhatInstance str = dump.getDumpedAhatInstance("charArray"); 105 assertEquals("char ", str.asString(5)); 106 } 107 108 @Test 109 public void asStringExactMax() throws IOException { 110 TestDump dump = TestDump.getTestDump(); 111 AhatInstance str = dump.getDumpedAhatInstance("basicString"); 112 assertEquals("hello, world", str.asString(12)); 113 } 114 115 @Test 116 public void asStringExactMaxNonAscii() throws IOException { 117 TestDump dump = TestDump.getTestDump(); 118 AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); 119 assertEquals("Sigma () is not ASCII", str.asString(22)); 120 } 121 122 @Test 123 public void asStringExactMaxEmbeddedZero() throws IOException { 124 TestDump dump = TestDump.getTestDump(); 125 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); 126 assertEquals("embedded\0...", str.asString(12)); 127 } 128 129 @Test 130 public void asStringCharArrayExactMax() throws IOException { 131 TestDump dump = TestDump.getTestDump(); 132 AhatInstance str = dump.getDumpedAhatInstance("charArray"); 133 assertEquals("char thing", str.asString(10)); 134 } 135 136 @Test 137 public void asStringNotTruncated() throws IOException { 138 TestDump dump = TestDump.getTestDump(); 139 AhatInstance str = dump.getDumpedAhatInstance("basicString"); 140 assertEquals("hello, world", str.asString(50)); 141 } 142 143 @Test 144 public void asStringNotTruncatedNonAscii() throws IOException { 145 TestDump dump = TestDump.getTestDump(); 146 AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); 147 assertEquals("Sigma () is not ASCII", str.asString(50)); 148 } 149 150 @Test 151 public void asStringNotTruncatedEmbeddedZero() throws IOException { 152 TestDump dump = TestDump.getTestDump(); 153 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); 154 assertEquals("embedded\0...", str.asString(50)); 155 } 156 157 @Test 158 public void asStringCharArrayNotTruncated() throws IOException { 159 TestDump dump = TestDump.getTestDump(); 160 AhatInstance str = dump.getDumpedAhatInstance("charArray"); 161 assertEquals("char thing", str.asString(50)); 162 } 163 164 @Test 165 public void asStringNegativeMax() throws IOException { 166 TestDump dump = TestDump.getTestDump(); 167 AhatInstance str = dump.getDumpedAhatInstance("basicString"); 168 assertEquals("hello, world", str.asString(-3)); 169 } 170 171 @Test 172 public void asStringNegativeMaxNonAscii() throws IOException { 173 TestDump dump = TestDump.getTestDump(); 174 AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); 175 assertEquals("Sigma () is not ASCII", str.asString(-3)); 176 } 177 178 @Test 179 public void asStringNegativeMaxEmbeddedZero() throws IOException { 180 TestDump dump = TestDump.getTestDump(); 181 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); 182 assertEquals("embedded\0...", str.asString(-3)); 183 } 184 185 @Test 186 public void asStringCharArrayNegativeMax() throws IOException { 187 TestDump dump = TestDump.getTestDump(); 188 AhatInstance str = dump.getDumpedAhatInstance("charArray"); 189 assertEquals("char thing", str.asString(-3)); 190 } 191 192 @Test 193 public void asStringNull() throws IOException { 194 TestDump dump = TestDump.getTestDump(); 195 AhatInstance obj = dump.getDumpedAhatInstance("nullString"); 196 assertNull(obj); 197 } 198 199 @Test 200 public void asStringNotString() throws IOException { 201 TestDump dump = TestDump.getTestDump(); 202 AhatInstance obj = dump.getDumpedAhatInstance("anObject"); 203 assertNotNull(obj); 204 assertNull(obj.asString()); 205 } 206 207 @Test 208 public void basicReference() throws IOException { 209 TestDump dump = TestDump.getTestDump(); 210 211 AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference"); 212 AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference"); 213 AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference"); 214 AhatInstance referent = dump.getDumpedAhatInstance("anObject"); 215 assertNotNull(pref); 216 assertNotNull(wref); 217 assertNotNull(nref); 218 assertNotNull(referent); 219 assertEquals(referent, pref.getReferent()); 220 assertEquals(referent, wref.getReferent()); 221 assertNull(nref.getReferent()); 222 assertNull(referent.getReferent()); 223 } 224 225 @Test 226 public void unreachableReferent() throws IOException { 227 // The test dump program should never be under enough GC pressure for the 228 // soft reference to be cleared. Ensure that ahat will show the soft 229 // reference as having a non-null referent. 230 TestDump dump = TestDump.getTestDump(); 231 AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference"); 232 assertNotNull(ref.getReferent()); 233 } 234 235 @Test 236 public void gcRootPath() throws IOException { 237 TestDump dump = TestDump.getTestDump(); 238 239 AhatClassObj main = dump.getAhatSnapshot().findClass("Main"); 240 AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray"); 241 Value value = gcPathArray.asArrayInstance().getValue(2); 242 AhatInstance base = value.asAhatInstance(); 243 AhatInstance left = base.getRefField("left"); 244 AhatInstance right = base.getRefField("right"); 245 AhatInstance target = left.getRefField("right"); 246 247 List<PathElement> path = target.getPathFromGcRoot(); 248 assertEquals(6, path.size()); 249 250 assertEquals(main, path.get(0).instance); 251 assertEquals(".stuff", path.get(0).field); 252 assertTrue(path.get(0).isDominator); 253 254 assertEquals(".gcPathArray", path.get(1).field); 255 assertTrue(path.get(1).isDominator); 256 257 assertEquals(gcPathArray, path.get(2).instance); 258 assertEquals("[2]", path.get(2).field); 259 assertTrue(path.get(2).isDominator); 260 261 assertEquals(base, path.get(3).instance); 262 assertTrue(path.get(3).isDominator); 263 264 // There are two possible paths. Either it can go through the 'left' node, 265 // or the 'right' node. 266 if (path.get(3).field.equals(".left")) { 267 assertEquals(".left", path.get(3).field); 268 269 assertEquals(left, path.get(4).instance); 270 assertEquals(".right", path.get(4).field); 271 assertFalse(path.get(4).isDominator); 272 273 } else { 274 assertEquals(".right", path.get(3).field); 275 276 assertEquals(right, path.get(4).instance); 277 assertEquals(".left", path.get(4).field); 278 assertFalse(path.get(4).isDominator); 279 } 280 281 assertEquals(target, path.get(5).instance); 282 assertEquals("", path.get(5).field); 283 assertTrue(path.get(5).isDominator); 284 } 285 286 @Test 287 public void retainedSize() throws IOException { 288 TestDump dump = TestDump.getTestDump(); 289 290 // anObject should not be an immediate dominator of any other object. This 291 // means its retained size should be equal to its size for the heap it was 292 // allocated on, and should be 0 for all other heaps. 293 AhatInstance anObject = dump.getDumpedAhatInstance("anObject"); 294 AhatSnapshot snapshot = dump.getAhatSnapshot(); 295 long size = anObject.getSize(); 296 assertEquals(size, anObject.getTotalRetainedSize()); 297 assertEquals(size, anObject.getRetainedSize(anObject.getHeap())); 298 for (AhatHeap heap : snapshot.getHeaps()) { 299 if (!heap.equals(anObject.getHeap())) { 300 assertEquals(String.format("For heap '%s'", heap.getName()), 301 0, anObject.getRetainedSize(heap)); 302 } 303 } 304 } 305 306 @Test 307 public void objectNotABitmap() throws IOException { 308 TestDump dump = TestDump.getTestDump(); 309 AhatInstance obj = dump.getDumpedAhatInstance("anObject"); 310 assertNull(obj.asBitmap()); 311 } 312 313 @Test 314 public void arrayNotABitmap() throws IOException { 315 TestDump dump = TestDump.getTestDump(); 316 AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray"); 317 assertNull(obj.asBitmap()); 318 } 319 320 @Test 321 public void classObjNotABitmap() throws IOException { 322 TestDump dump = TestDump.getTestDump(); 323 AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); 324 assertNull(obj.asBitmap()); 325 } 326 327 @Test 328 public void classInstanceToString() throws IOException { 329 TestDump dump = TestDump.getTestDump(); 330 AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference"); 331 long id = obj.getId(); 332 assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString()); 333 } 334 335 @Test 336 public void classObjToString() throws IOException { 337 TestDump dump = TestDump.getTestDump(); 338 AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); 339 assertEquals("Main", obj.toString()); 340 } 341 342 @Test 343 public void arrayInstanceToString() throws IOException { 344 TestDump dump = TestDump.getTestDump(); 345 AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray"); 346 long id = obj.getId(); 347 348 // There's a bug in perfib's proguard deobfuscation for arrays. 349 // To work around that bug for the time being, only test the suffix of 350 // the toString result. Ideally we test for string equality against 351 // "Main$ObjectTree[4]@%08x", id. 352 assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id))); 353 } 354 355 @Test 356 public void primArrayInstanceToString() throws IOException { 357 TestDump dump = TestDump.getTestDump(); 358 AhatInstance obj = dump.getDumpedAhatInstance("bigArray"); 359 long id = obj.getId(); 360 assertEquals(String.format("byte[1000000]@%08x", id), obj.toString()); 361 } 362 363 @Test 364 public void isNotRoot() throws IOException { 365 TestDump dump = TestDump.getTestDump(); 366 AhatInstance obj = dump.getDumpedAhatInstance("anObject"); 367 assertFalse(obj.isRoot()); 368 assertNull(obj.getRootTypes()); 369 } 370 371 @Test 372 public void asStringEmbedded() throws IOException { 373 // Set up a heap dump with an instance of java.lang.String of 374 // "hello" with instance id 0x42 that is backed by a char array that is 375 // bigger. This is how ART used to represent strings, and we should still 376 // support it in case the heap dump is from a previous platform version. 377 HprofStringBuilder strings = new HprofStringBuilder(0); 378 List<HprofRecord> records = new ArrayList<HprofRecord>(); 379 List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>(); 380 381 final int stringClassObjectId = 1; 382 records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String"))); 383 dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0, 384 new HprofConstant[0], new HprofStaticField[0], 385 new HprofInstanceField[]{ 386 new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT), 387 new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT), 388 new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT), 389 new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)})); 390 391 dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR, 392 new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'})); 393 394 ByteArrayDataOutput values = ByteStreams.newDataOutput(); 395 values.writeInt(5); // count 396 values.writeInt(0); // hashCode 397 values.writeInt(4); // offset 398 values.writeInt(0x41); // value 399 dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray())); 400 dump.add(new HprofRootDebugger(stringClassObjectId)); 401 dump.add(new HprofRootDebugger(0x42)); 402 403 records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0]))); 404 AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records); 405 AhatInstance chars = snapshot.findInstance(0x41); 406 assertNotNull(chars); 407 assertEquals("not helloop", chars.asString()); 408 409 AhatInstance stringInstance = snapshot.findInstance(0x42); 410 assertNotNull(stringInstance); 411 assertEquals("hello", stringInstance.asString()); 412 } 413 } 414